Реализовать `memcpy ()`: нужен `unsigned char *` или просто `char *`? - PullRequest
0 голосов
/ 03 марта 2019

Я реализовывал версию memcpy(), чтобы иметь возможность использовать ее с volatile.Безопасно ли использовать char * или мне нужно unsigned char *?

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
    const volatile char *src_c  = (const volatile char *)src;
    volatile char *dest_c       = (volatile char *)dest;

    for (size_t i = 0; i < n; i++) {
        dest_c[i]   = src_c[i];
    }

    return  dest;
}

Я думаю, unsigned должно быть необходимо, чтобы избежать проблем переполнения, если данные в любой ячейке буфера равны > INT8_MAXЯ думаю, что это UB.

Ответы [ 4 ]

0 голосов
/ 03 марта 2019

unsigned не необходим , но нет никакой причины использовать простой char для этой функции.Обычная char должна использоваться только для реальных строк символов.Для других целей типы unsigned char или uint8_t и int8_t являются более точными, поскольку подпись явно указана.

Если вы хотите упростить код функции, вы можете удалить приведение:

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n) {
    const volatile unsigned char *src_c = src;
    volatile unsigned char *dest_c = dest;

    for (size_t i = 0; i < n; i++) {
        dest_c[i] = src_c[i];
    }
    return dest;
}
0 голосов
/ 03 марта 2019

Теоретически, ваш код может работать на машине, которая запрещает один битовый шаблон в char со знаком.Он может использовать свои представления дополнения или величины знака отрицательных целых чисел, в которых один битовый шаблон будет интерпретироваться как 0 с отрицательным знаком.Даже на архитектурах с двумя дополнениями стандарт позволяет реализации ограничивать диапазон отрицательных целых чисел так, чтобы INT_MIN == -INT_MAX, хотя я не знаю ни одной реальной машины, которая делает это.

Итак, согласно §6.2.6.2p2, может быть одно значение со знаком, которое реализация может рассматривать как представление прерывания:

Какое из этих [представлений отрицательных целых чисел] применяется, определяется реализацией, как и является лизначение со знаковым битом 1 и всеми битами значения ноль (для первых двух [знаковая величина и дополнение к двум]) или со знаковым битом и всеми битами значения 1 (для дополнения единиц) является представлением прерывания или нормальным значением.В случае знака, величины и их дополнения, если это представление является нормальным значением, оно называется отрицательный ноль .

(не может быть никаких других значений ловушек длятипы символов, потому что §6.2.6.2 требует, чтобы signed char не имел никаких битов заполнения, что является единственным другим способом, которым может быть сформировано представление прерывания. По той же причине ни один битовый шаблон не является представлением прерывания для unsigned char.)

Итак, если эта гипотетическая машина имеет реализацию C, в которой подписано char, то возможно, что копирование произвольного байта через char потребует копирования представления прерывания.

Для целочисленных типов со знаком, кроме char (если оно подписано) и signed char, чтение значения, представляющего собой ловушку, является неопределенным поведением.Но §6.2.6.1 / 5 позволяет читать и записывать эти значения только для символьных типов :

Некоторые представления объекта не должны представлять значение типа объекта.Если сохраненное значение объекта имеет такое представление и читается выражением lvalue , которое не имеет символьного типа , поведение не определено.Если такое представление создается побочным эффектом, который изменяет весь или любую часть объекта выражением lvalue, которое не имеет символьного типа, поведение не определено.Такое представление называется представлением ловушек. (выделение добавлено)

(Третье предложение немного неуклюже, но для упрощения: сохранение значения в памяти - это «побочный эффект, который изменяет весь объект»,так что это разрешено.)

Короче говоря, благодаря этому исключению, вы можете использовать char в реализации memcpy, не беспокоясь о неопределенном поведении.

Однако то же самоене верно для strcpy.strcpy должен проверить завершающий байт NUL, который завершает строку, что означает, что ему нужно сравнить значение, которое он читает из памяти, с 0. И операторы сравнения (в действительности, все арифметические операторы) сначала выполняют целочисленное продвижение своих операндов,преобразует char в int.Целочисленное продвижение представления ловушек - это неопределенное поведение, насколько я знаю, поэтому в гипотетической реализации C, работающей на гипотетической машине, вам потребуется использовать unsigned char, чтобы реализовать strcpy.

0 голосов
/ 03 марта 2019

Безопасно ли использовать char * или мне нужно unsigned char *?

Возможно


Функции "обработки строк", такие как memcpy() имеют спецификацию:

Для всех функций в этом подпункте каждый символ должен интерпретироваться так, как если бы он имел тип unsigned char (и, следовательно, каждое возможное представление объекта является допустимым и имеет другое значение),C11dr §7.23.1 3

Использование unsigned char является указанным типом «как будто».Мало что можно получить, пытаясь других - что может или не может работать.


Использование char с memcpy() может работать, но расширение этой парадигмы на другие подобные функции приводит кпроблемы.

Единственная серьезная причина, по которой следует избегать char для str...() и mem...() подобных функций, заключается в том, что иногда это неожиданно приводит к функциональной разнице.

memcmp(), strcmp() определенно отличается от ( со знаком ) char против unsigned char.

Педантичный: на дополнении к реликвии не-2 с со знаком char, только'\0' должно заканчиваться строкой .Тем не менее, negative_zero == 0 тоже и char с negative_zero не должны указывать конец строки .

0 голосов
/ 03 марта 2019

Вам не нужно unsigned.

Примерно так:

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
    const volatile char *src_c  = (const volatile char *)src;
    volatile char *dest_c       = (volatile char *)dest;

    for (size_t i = 0; i < n; i++) {
        dest_c[i]   = src_c[i];
    }

    return  dest;
}

Попытка сделать подтверждающую реализацию, где char имеет значение ловушки, в конечном итоге приведет к противоречию:

  • fopen ("", "rb") не требует использования только fread(), а fwrite()
  • fgets() принимает char * в качестве первого аргумента иможет использоваться в двоичных файлах.
  • strlen() находит расстояние до следующего нуля из заданного char *.Поскольку fgets() гарантированно записал его, он не будет читать за концом массива и, следовательно, не будет ловить
...