C правила сглаживания и memcpy

Отвечая на другой вопрос, я подумал о следующем примере:

void *p;
unsigned x = 17;

assert(sizeof(void*) >= sizeof(unsigned));
*(unsigned*)&p = 17;        // (1)
memcpy(&p, &x, sizeof(x));  // (2)

Строка 1 нарушает правила псевдонимов. Строка 2, однако, в порядке. правила псевдонимов. Вопрос в том, почему? Имеет ли компилятор специальные встроенные знания о таких функциях, как memcpy, или существуют ли другие правила, которые делают memcpy ОК? Есть ли способ реализации memcpy-подобных функций в стандарте C без нарушения правил псевдонимов?

Ответ 1

Стандарт C вполне понятен. Эффективным типом объекта, названным p, является void*, потому что он имеет объявленный тип, см. 6.5/6. Правила сглаживания в C99 применяются к чтению и записи, а запись в void* через unsigned lvalue в (1) - это поведение undefined в соответствии с 6.5/7.

В отличие от memcpy of (2) отлично, потому что unsigned char* может псевдоним любого объекта (6.5/7). Стандарт определяет memcpy в 7.21.2/1 как

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

Функция memcpy копирует n символов из объекта, на который указывает s2, в объект, на который указывает s1. Если копирование происходит между перекрывающимися объектами, поведение не определено.

Однако, если после этого существует использование p, это может привести к поведению undefined в зависимости от битпаттерна. Если такого использования не происходит, этот код в порядке. C.


В соответствии с Стандартом С++, который, на мой взгляд, далеко не ясен в этом вопросе, я считаю, что следующее. Пожалуйста, не принимайте эту интерпретацию как единственно возможную - неопределенная/неполная спецификация оставляет много места для спекуляций.

Строка (1) проблематична, поскольку выравнивание &p может быть не подходит для типа unsigned. Он изменяет тип объекта, хранящегося в p, на unsigned int. До тех пор, пока вы не получите доступ к этому объекту позже через p, правила сглаживания не будут нарушены, но требования по выравниванию все равно могут быть.

Строка (2), однако, не имеет проблем с выравниванием и, следовательно, действительна, если вы не получаете доступ к p впоследствии как void*, что может вызвать поведение undefined в зависимости от того, как void* тип интерпретирует хранимый битпаттерн. Я не думаю, что тип объекта изменяется таким образом.

Существует длинный GCC Bugreport, в котором также обсуждаются последствия записи через указатель, полученный в результате такого приведения, и какая разница Размещение - новое (люди в этом списке не согласны с тем, что это такое).