Гарантируется ли безопасное выполнение memcpy (0,0,0)? - PullRequest
69 голосов
/ 09 марта 2011

Я не очень хорошо разбираюсь в стандарте C, поэтому, пожалуйста, потерпите меня.

Я хотел бы знать, гарантируется ли стандартом, что memcpy(0,0,0) безопасно.

Единственное ограничение, которое я смог найти, это то, что если области памяти перекрываются, то поведение не определено ...

Но можем ли мы считать, что области памяти здесь перекрываются?

Ответы [ 4 ]

67 голосов
/ 09 марта 2011

У меня есть черновая версия стандарта C (ISO / IEC 9899: 1999), и есть несколько забавных слов, чтобы сказать об этом звонке.Для начала, он упоминает (§7.21.1 / 2) в отношении memcpy, что

Где аргумент, объявленный как size_t n, указывает длину массива для функции, n можетиметь значение ноль при вызове этой функции.Если явно не указано иное в описании конкретной функции в этом подпункте, аргументы указателя при таком вызове должны по-прежнему иметь действительные значения, как описано в 7.1.4 .При таком вызове функция, которая находит символ, не находит вхождения, функция, которая сравнивает две последовательности символов, возвращает ноль, а функция, которая копирует символы, копирует ноль символов.

Ссылка, указанная здесь, указывает наthis:

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

Таким образом, согласно спецификации C, вызов

memcpy(0, 0, 0)

приводит к неопределенному поведению, поскольку нулевые указатели считаются «недопустимыми значениями».

Тем не менее, я был бы крайне удивлен, если бы какая-либо реальная реализация memcpy сломалась, если бы вы сделали это, так как большинствоИнтуитивные реализации, о которых я могу думать, ничего бы не сделали, если бы вы сказали скопировать ноль байтов.

21 голосов
/ 12 июля 2014

Ради интереса заметки о выпуске gcc-4.9 указывают, что их оптимизатор использует эти правила и, например, может удалить условное выражение в

int copy (int* dest, int* src, size_t nbytes) {
    memmove (dest, src, nbytes);
    if (src != NULL)
        return *src;
    return 0;
}

, который затем дает неожиданные результаты при вызове copy(0,0,0) (см. https://gcc.gnu.org/gcc-4.9/porting_to.html).

Я немного амбивалентен в отношении поведения gcc-4.9; поведение может соответствовать стандартам, но возможность вызова memmove (0,0,0) иногда является полезным расширением этих стандартов.

0 голосов
/ 10 января 2019

Нет, memcpy(0,0,0) не безопасно.Стандартная библиотека, скорее всего, не потерпит неудачу при этом вызове.Однако в тестовой среде в memcpy () может присутствовать дополнительный код для обнаружения переполнения буфера и других проблем.И как эта специальная версия memcpy () реагирует на указатели NULL, ну, в общем, не определено.

0 голосов
/ 12 августа 2017

Вы также можете рассмотреть использование memmove в Git 2.14.x (3-й квартал 2017 г.)

См. коммит 168e635 (16 июля 2017 г.) и коммит 1773664, коммит f331ab9 , коммит 5783980 (15 июля 2017 г.) Рене Шарф (rscharfe) .
(объединено Junio ​​C Hamano - gitster - in commit 32f9025 , 11 августа 2017 г.)

Используется вспомогательный макрос MOVE_ARRAY, который вычисляет размер на основе указанного количества элементов для нас и поддерживает указатели NULL, когда это число равно нулю.
Raw memmove(3) вызовы с NULL могут вызвать компилятор(с чрезмерным нетерпением) оптимизировать последующие NULL проверки.

MOVE_ARRAY добавляет безопасный и удобный помощник для перемещения потенциально перекрывающихся диапазонов записей массива.
Он выводит размер элемента, умножает автоматически и безопасно для получения размера в байтах, выполняет базовую проверку безопасности типа, сравнивая размеры элемента и в отличие от memmove(3)он поддерживает NULL указатели, если необходимо переместить 0 элементов.

#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
    BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
    if (n)
        memmove(dst, src, st_mult(size, n));
}

Примеры :

- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);

Используется макрос BUILD_ASSERT_OR_ZERO, которая утверждает зависимость во время сборки, как выражение (где @cond является условием времени компиляции, которое должно быть истинным).
Компиляция не будет выполнена, если условие не выполнено, илине может быть вычислено компилятором.

#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)

Пример:

#define foo_to_char(foo)                \
     ((char *)(foo)                     \
      + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
...