Безопасно ли выполнять memcpy (0,0,0)?

80

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

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

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

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

Матье М.
источник
7
Математически пересечение двух пустых множеств пусто.
Benoit
Я хотел проверить, хотите ли вы, что (x) libC подходит для вас, но поскольку это asm (здесь elibc / glibc), это слишком сложно для раннего утра :)
Кевин
16
+1 Мне нравится этот вопрос как потому, что это такой странный крайний случай, так и потому, что я думаю, что memcpy(0,0,0)это один из самых странных фрагментов кода C, который я видел.
templatetypedef
2
@eq Вы действительно хотите знать, или вы подразумеваете, что нет ситуаций, в которых вы бы этого хотели? Вы думали, что на самом деле звонок может быть, скажем memcpy(outp, inp, len),? И что это могло произойти в коде, где outpи inpразмещаются динамически и изначально 0? Это работает, например, с p = realloc(p, len+n)when pи lenare 0. Я сам использовал такой memcpyвызов - хотя технически это UB, я никогда не сталкивался с реализацией, в которой он не запрещал бы операцию и никогда не ожидал бы этого.
Джим Балтер,
7
@templatetypedef memcpy(0, 0, 0), скорее всего, предназначен для представления динамического, а не статического вызова ... т. е. значения этих параметров не обязательно должны быть литералами.
Джим Балтер,

Ответы:

71

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

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

Указанная здесь ссылка указывает на это:

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

Итак, согласно спецификации C, вызов

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

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

templatetypedef
источник
3
Могу подтвердить, что цитируемые части проекта стандарта идентичны в итоговом документе. С таким вызовом не должно быть никаких проблем, но вы все равно полагаетесь на неопределенное поведение. Так что ответ на «гарантировано ли» - «нет».
DevSolar
8
Никакая реализация, которую вы когда-либо будете использовать в производстве, не приведет к чему-либо, кроме бездействия для такого вызова, но реализации, которые делают иначе, разрешены и разумны ... например, интерпретатор C или расширенный компилятор с проверкой ошибок, который отклоняет звоните, потому что это не соответствует требованиям. Конечно, это было бы неразумно, если бы Стандарт разрешал вызов, как это делается для realloc(0, 0). Сценарии использования схожи, и я использовал их оба (см. Мой комментарий под вопросом). Бессмысленно и прискорбно, что Стандарт делает этот UB.
Джим Балтер,
5
«Я был бы крайне удивлен, если бы какая-либо реальная реализация memcpy сломалась, если бы вы сделали это» - я использовал такую, которая могла бы; фактически, если вы передали длину 0 с действительными указателями, она фактически скопировала 65536 байт. (Его цикл уменьшил длину, а затем проверил).
MM
14
@MattMcNabb Эта реализация не работает.
Джим Балтер
3
@MattMcNabb: Может быть, добавить «правильно» к «фактическому». Я думаю, что у всех нас не очень приятные воспоминания о старых библиотеках C из гетто, и я не уверен, многие из нас ценят эти воспоминания. :)
tmyklebu 09
24

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

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

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

user1998586
источник
2
Интересно. Я понимаю вашу двойственность, но это суть оптимизаций в C: компилятор предполагает, что разработчики следуют определенным правилам, и, таким образом, делает вывод, что некоторые оптимизации допустимы (а они и есть, если правила соблюдаются).
Matthieu M.
2
@tmyklebu: Учитывая char *p = 0; int i=something;, что оценка выражения (p+i)приведет к неопределенному поведению, даже если iоно равно нулю.
supercat 06
1
@tmyklebu: ИМХО было бы хорошо иметь всю арифметику указателей (кроме сравнений) в ловушке с нулевым указателем; ли memcpy()должны быть разрешено выполнять любые арифметические операции над указателями на своих аргументах предшествующих обеспечению подсчета ненулевого другой вопрос [если я проектировал стандарты, я бы , вероятно , указать , что если pимеет нулевое значение, p+0может ловушка, но memcpy(p,p,0)не буду делать ничего]. Гораздо более серьезная проблема, IMHO, - это неограниченность большинства неопределенных поведений. Хотя есть некоторые вещи, которые действительно должныfree(p)
отражать
1
... и последующее выполнение p[0]=1;) есть много вещей, которые должны быть указаны как дающие неопределенный результат (например, реляционное сравнение между несвязанными указателями не должно указываться как согласованное с любым другим сравнением, а должно быть указано как дающее либо 0 или 1), или должно быть указано как приводящее к более свободному поведению, чем определенное реализацией (компиляторы должны быть обязаны документировать все возможные последствия, например, целочисленного переполнения, но не указывать, какие последствия могут произойти в каждом конкретном случае).
supercat
8
кто-нибудь, пожалуйста, скажите мне, почему я не получаю значок stackoverflow "начал
пламенную
0

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

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

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

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

Примеры :

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

Пример:

VonC
источник
2
Существование оптимизаторов, которые думают, что «умный» и «тупой» - антонимы, делает тест на n необходимым, но более эффективный код обычно возможен в реализации, которая гарантировала бы, что memmove (any, any, 0) не будет работать . Если компилятор не может заменить вызов memmove () на вызов memmoveAtLeastOneByte (), обходной путь для защиты от «оптимизации» умных / глупых компиляторов обычно приводит к дополнительному сравнению, которое компилятор не сможет устранить.
supercat
-3

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

Кай Петцке
источник
Я не вижу ничего, что ваш ответ добавляет к существующим ответам, которые уже указывали, что это небезопасно, с выдержками из стандарта.
Matthieu M.
Я написал причину, по которой некоторые реализации memcpy («в тестовой среде») могут приводить к проблемам с memcpy (0, 0, 0). Я думаю, это ново.
Кай Петцке