Предполагая, что unsigned int не имеет ловушечных представлений, выполните одно или оба из приведенных ниже выражений (A) и (B), спровоцируйте поведение undefined, почему или почему нет, и (особенно, если вы считаете, что один из них хорошо -defined, но другой нет), считаете ли вы, что дефект в стандарте? Меня в первую очередь интересует текущая версия стандарта C (т.е. C2011), но если это отличается в старых версиях стандарта или на С++, я также хотел бы узнать об этом.
(_Alignas используется в этой программе, чтобы исключить любой вопрос UB из-за неадекватного выравнивания. Однако правила, которые я обсуждаю в своей интерпретации, не говорят о выравнивании.)
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned int v1, v2;
unsigned char _Alignas(unsigned int) b1[sizeof(unsigned int)];
unsigned char *b2 = malloc(sizeof(unsigned int));
if (!b2) return 1;
memset(b1, 0x55, sizeof(unsigned int));
memset(b2, 0x55, sizeof(unsigned int));
v1 = *(unsigned int *)b1; /* (A) */
v2 = *(unsigned int *)b2; /* (B) */
return !(v1 == v2);
}
Моя интерпретация C2011 заключается в том, что (A) вызывает поведение undefined, но (B) хорошо определен (для хранения неопределенного значения в v2), потому что:
-
memsetопределяется (§7.24.6.1) для записи в свой первый аргумент as-if через lvalue с типом символа, который разрешен как дляb1, так иb2для специального случая в нижняя часть §6.5p7. -
Объект
b1имеет объявленный типunsigned char[n]. Поэтому его эффективный тип для доступа такжеunsigned char[n]на 6.5p6. Оператор (A) читаетb1через выражение lvalue, тип которогоunsigned int, который не является эффективным типомb1или любым из других исключений в 6.5p7, поэтому поведение undefined. -
Объект, на который указывает
b2, не имеет объявленного типа. Значение, сохраненное в нем (черезmemset), было (как-если) через lvalue с типом символа, поэтому второй случай 6.5p6 не применяется. Значение не было скопировано нигде, поэтому третий случай 6.5p6 также не применяется. Поэтому эффективным типом объекта является тип lvalue, используемого для доступа, который равенunsigned int, и выполняются правила 6.5p7. -
Наконец, в соответствии с 6.2.6.1, если
unsigned intне имеет ловушечных представлений, операцияmemsetсоздала представление некоторого неопределенного значенияunsigned intв каждом изb1иb2. Поэтому, если ни (A), ни (B) не провоцируют поведение undefined, то фактические значения вv1иv2не определены, но они равны.
Комментарий:
Асимметрия правил "наложения на основе типа" (т.е. 6.5p7), разрешающая доступ к объекту с любым эффективным типом доступа с помощью символьного типа, но не наоборот, является постоянным источником путаницы, Второй случай 6.5p6, кажется, был добавлен специально, чтобы предотвратить его поведение undefined для чтения значения, инициализированного memset (или, если на то пошло, calloc), но, поскольку оно применимо только к объектам без объявленный тип, сам по себе является дополнительным источником путаницы.