Правила сглаживания C и memcpy
-
17-09-2020 - |
Вопрос
Отвечая на другой вопрос, я подумал о следующем примере:
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 (люди в этом списке не согласны с тем, что это такое).