Является memcpy (& a + 1, & b + 1, 0), определенным в C11?

Этот вопрос следует этому предыдущему вопросу об определенности memcpy(0, 0, 0), который был окончательно определен как поведение undefined.

Как показывает связанный вопрос, ответ зависит от содержания предложения C11 7.1.4: 1

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

Стандартная функция memcpy() ожидает указателей на void и const void, так как:

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

Вопрос стоит задать вовсе не потому, что в стандарте есть два понятия "правильные" указатели: есть указатели, которые могут быть достоверно получены с помощью арифметики указателя и могут быть справедливо сопоставлены с <, > другим указателям внутри одного и того же объекта. И есть указатели, которые действительны для разыменования. Первый класс включает в себя "одно-прошлое" указатели, такие как &a + 1 и &b + 1 в следующем фрагменте, тогда как последний класс не включает их как действительные.

char a;
const char b = '7';
memcpy(&a + 1, &b + 1, 0);

Если приведенный выше фрагмент считается определенным поведением, в свете того факта, что аргументы memcpy() в любом случае набираются как указатели на void, поэтому вопрос об их соответствующих действиях не может быть о разыменовании их. Или следует считать &a + 1 и &b + 1 "вне адресного пространства программы"?

Это имеет значение для меня, потому что я в процессе формализации эффектов стандартных функций C. Я написал одно предварительное условие memcpy() как requires \valid(s1+(0 .. n-1));, пока не было указано на мое внимание, что GCC 4.9 начал агрессивно оптимизируйте вызовы функций библиотеки за пределами того, что выражено в приведенной выше формуле (действительно). Формула \valid(s1+(0 .. n-1)) в этом конкретном языке спецификации эквивалентна true, когда n является 0, и не фиксирует поведение undefined, которое GCC 4.9 полагается для оптимизации.

Ответ 1

C11 говорит:

(C11, 7.24.2.1p2) "Функция memcpy копирует n символов из объекта, на который указывает s2, в объект, на который указывает s1."

&a + 1 сам является допустимым указателем на целочисленное добавление, но &a + 1 не является указателем на объект, поэтому вызов вызывает поведение undefined.

Ответ 2

В то время как "правильный" ответ в соответствии со стандартом, похоже, не согласен, я могу найти его только неискренним, что после int a [6]; int b [6]; все

memcpy(a+0, b+0, 6);
memcpy(a+1, b+1, 5);
memcpy(a+2, b+2, 4);
memcpy(a+3, b+3, 3);
memcpy(a+4, b+4, 2);
memcpy(a+5, b+5, 1);

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

memcpy(a+6, b+6, 0);

действует в свете подсчета, но не адресов. Это тот же конец скопированной области!

Лично я бы склонялся к тому, чтобы определить memcpy (0,0,0) как действительный (с обоснованием просто требуемых действительных указателей, но без объектов), но по крайней мере это особый случай, когда "конец массива" case является фактическим исключением из обычного шаблона для копирования области в конце массива.