Это код memmove
из https://github.com/gcc-mirror/gcc/blob/master/libgcc/memmove.c
void *
memmove (void *dest, const void *src, size_t len)
{
char *d = dest;
const char *s = src;
if (d < s)
while (len--)
*d++ = *s++;
else
{
char *lasts = s + (len-1);
char *lastd = d + (len-1);
while (len--)
*lastd-- = *lasts--;
}
return dest;
}
Стандарт C11 указан в 6.5.8. Реляционные операторы:
При сравнении двух указателей результат зависит от относительных расположений в адресное пространство объектов, на которые указывает. Если два указателя на типы объектов указывают на один и тот же объект или оба указывают на один после последнего элемента одного и того же объекта массива, они сравнивать равные. Если объекты, на которые указывает ссылка, являются членами одного и того же агрегатного объекта, указатели на члены структуры, объявленные позже, сравниваются больше, чем указатели на члены объявленные ранее в структуре, и указатели на элементы массива с большим индексом. значения сравниваются больше, чем указатели на элементы того же массива с меньшими значениями индекса. Все указатели на члены одного и того же объекта объединения сравниваются равными. Если выражение P указывает на элемент объекта массива, а выражение Q указывает на элемент последний элемент того же объекта массива, выражение указателя Q+1 сравнивается больше, чем П. Во всех остальных случаях поведение неопределенное.
Похоже, что приведенный выше код сравнивает указатели на два разных объекта:
if (d < s)
то есть поведение неопределенное, это правда? Или сравнение указателей с char
четко определено?
@IanAbbott, что, если бы это была моя реализация memmove
, например. Я не использую версию стандартной библиотеки и вместо этого написал то же самое в своем собственном файле .c? А это, похоже, не так implementation defined
@ k1r1t0 GCC (и, вероятно, большинство других компиляторов) будет вести себя так, как ожидалось, даже если он не определен в соответствии со стандартом: они будут сравнивать числовые (полностью упорядоченные) адреса, представленные указателями. Если вам не нужна 100% переносимость, то есть строго соответствующая программа, то я думаю, что это нормальное предположение.
Если это ваша собственная функция, то она будет иметь UB, потому что в стандарте нет четко определенного способа относительного сравнения случайных допустимых значений указателя. Их можно сравнивать только на предмет (не)равенства.
Четко определенный способ - сначала проверить перекрытие, перебирая диапазон источника/назначения и сравнивая его с началом соответствующего другого диапазона с помощью ==
вместо использования <
.
На практике большинство программ не будут использовать memmove
из libgcc
, а будут использовать одну из основной библиотеки времени выполнения C в цепочке инструментов (например, glibc или msvcrt). Он будет более оптимизирован по производительности, чем простой вариант в libgcc
.
@user17732522 user17732522 Да, это сработает, но потребует больше накладных расходов, чем запрещенный реляционный тест.
@k1r1t0: Re «Кроме того, это не похоже на implementation defined
»: «Реализация определена» означает, что стандарт C требует реализации для документирования чего-либо. Однако нет никаких запретов на реализацию, определяющую все, что она захочет. Если что-то не определено стандартом C, это не означает, что оно должно оставаться неопределенным. Это только означает, что стандарт C не определяет его. Реализация совершенно свободна в его определении. Неопределенное поведение не означает, что вы не должны что-то делать; это только означает, что стандарт C ничего об этом не говорит.
@k1r1t0 — Исторически C был определен для обработки сегментированной памяти, где указатель состоит из двух частей Segment:Offset
. Часть «Сегмент» представляет собой индекс таблицы дескрипторов и ничего не говорит о том, где находится блок памяти. Это также можно изменить, изменив таблицу, не меняя указатель. Таким образом, сравнение сегментов с <
становится бессмысленным. Стандарт отражает это, не говоря о том, что это означает.
Реализации стандартной библиотеки C могут потребовать, чтобы компиляторы, с которыми они работают в паре, определяли определенное поведение. Другими словами, реализация библиотеки может заявить: «Эта библиотека полагается на тот факт, что компилятор поддерживает реляционные операторы для любых двух указателей на типы объектов», а затем может использовать это в своем коде.1
Тот факт, что стандарт C не определяет поведение, не запрещает реализации C определять поведение и не запрещает реализации стандартной библиотеки C требовать его определения.
то есть поведение неопределенное, это правда? Или сравнение указателей с символами четко определено?
Что-то может быть как неопределенным, так и полностью определенным.
Это связано с тем, что в контексте C слово undefine обычно используется для обозначения неопределенного поведения в том смысле, который указан в стандарте C. В этом смысле слово имеет только одно значение:
поведение при использовании непереносимой или ошибочной программной конструкции или ошибочных данных, к которым данный документ не предъявляет никаких требований [C 2018 3.4.3 1].
Вот некоторые вещи, которые не означает слово «неопределенное» или словосочетание «неопределенное поведение»:
Это не означает, что вы не можете использовать конструкцию или данные.
Это не означает, что реализация C не может определять поведение.
Это не означает, что вы не можете исследовать и рассуждать о поведении, основываясь на вещах, выходящих за рамки стандарта C, таких как операционная система, конструкция аппаратного обеспечения или законы физики.
Таким образом, реализация C может определять «неопределённое» поведение. Тогда это поведение не определено стандартом C, но определено реализацией C.
1 Реализации библиотеки часто плохо документированы и могут не содержать таких требований явно или ясно. Тем не менее, это является основанием для того, чтобы библиотека могла использовать вещи, которые не определены только стандартом C.
@Эрик Постпишил, разве ты не знаешь, есть ли флаг, который может контролировать такое поведение, особенно для сравнения двух указателей? Я нашел только -Wstrict-aliasing
и -Wno-pointer-compare
, но, похоже, они не контролируют это поведение, особенно для GCC.
Функции стандартной библиотеки не ограничены этими правилами, поскольку они являются частью реализации.