Вопрос

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

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 без нарушения правил псевдонимирования?

Это было полезно?

Решение

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

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

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

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

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


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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top