6.5(p7)
上有一个关于union
和aggregate
的项目符号:
一个对象的存储值只能由左值访问 具有以下类型之一的表达式:
[...]
-包含上述类型之一的集合或联合类型 其成员之间的类型(包括,递归地, 子集合或包含的并集),或
这还不是很清楚。是否需要至少一个成员或所有成员来满足严格的别名规则。特别是关于union
的内容:
union aliased{
unsigned char uint64_repr[sizeof(uint64_t)];
uint64_t value;
};
int main(int args, const char *argv[]){
uint64_t some_random_value = 123;
union aliased alias;
memcpy(&(alias.uint64_repr), &some_random_value, sizeof(uint64_t));
printf("Value = %" PRIu64 "\n", alias.value);
}
程序的行为是否定义明确?如果没有,子弹是什么意思?
答案 0 :(得分:3)
这意味着使用union
是避免类型转换的一种标准兼容方式,如果您尝试通过其他类型的指针访问存储的值,则可能会发生严格的别名冲突。
以unsigned
和float
为例,通常都是32位,在某些情况下可能需要从unsigned*
或float*
查看存储的值。例如,您不能:
float f = 3.3;
// unsigned u = *(unsigned *)&f; /* violation */
在6.5(p7)之后,您可以在两种类型之间使用union
,并访问与unsigned
或float
相同的信息,而无需对指针进行类型操作或操作。严格的别名规则,例如
typedef union {
float f;
unsigned u;
} f2u;
...
float f = 3.3;
// unsigned u = *(unsigned *)&f; /* violation */
f2u fu = { .f = f };
unsigned u = fu.u; /* OK - no violation */
因此,严格的别名规则会阻止通过另一种类型的指针访问具有有效类型的内存,除非该指针是char
类型或指向这两种类型之间的并集成员的指针。
(注意:),该标准部分仅是示例性的例子(您可以阅读10次,但仍会伤脑筋)。其目的是为了遏制滥用指针类型,同时仍然认识到必须能够通过字符类型访问任何形式的内存块((union
是其他允许的访问方式)。)
在过去几年中,标记违规行为的编译器变得越来越好。
答案 1 :(得分:2)
要点有两个目的。首先,如果认识到对基于或可能明显基于特定类型左值的左值的访问应被识别为后一种类型的左值或可能的左值,则给出以下内容:
union U {int x[10]; float y[10];} u;
从u
明显派生的左值将被允许访问其中包含的所有对象。实现可以识别左值是基于另一个值的情况范围是实现质量问题,某些高质量的编译器(例如icc)能够识别,给出以下信息:
int load_array_element(int *array, int i) { return array[i]); }
...
int test(int i) { return load_array_element(&u.x, i); }
specific 调用load_array_element
可能与*array
所做的任何事情都将由u
完成(它被赋予一个左值的地址,该地址直接由毕竟,u
和clang和gcc之类的其他编译器甚至都无法将*(u.x+i)
之类的结构识别为基于u
的左值。
项目符号的第二个目的是建议,即使编译器太原始了,无法跟踪直线代码中的左值派生,它也应认识到给定的声明:
int *p,i;
struct foo { int x;} foo;
如果看到*p=1; i=foo.x;
而没有注意p
的来源,则必须确保在读取*p
之前执行对foo.x
的写操作。即使只有在真正麻烦的编译器能够看到p
由foo
构成的情况下才真正有必要,用这些术语描述事物也会增加与访问foo.x
相比,编译器的明显复杂性迫使完成对整数指针目标的所有挂起写操作。
请注意,如果仅对通过新派生的指针访问struct或union成员感兴趣的情况,则无需包括一般权限即可通过成员类型的lvalue访问struct或union对象。给定代码序列:foo.x = 1; p = &foo.x; i=*p;
,采用地址foo.x
的行为应使编译器在运行任何可能使用该地址的代码之前完成对foo.x
的所有未决写操作(编译器不知道下游代码将如何处理该地址就可以立即完成写入操作)。如果代码序列为foo.x = 1; i = *p;
,则通过左值foo.x
访问foo
的行为将意味着任何可能标识该存储的现有指针都将“过时”,因此编译器将没有义务承认这样的指针可能标识与foo.x
相同的存储。
请注意,尽管脚注88明确指出“严格别名规则”的目的是指定何时允许对象别名,但gcc和clang的解释将规则视为忽略对象为对象的借口由非常明显地从它们派生的左值访问。回顾一下,标准的作者可能应该包含一条规定:“请注意,该规则不禁止低质量的编译器以钝角的方式运行,但无意引起这种行为”,但是C89的作者没有理由期望规则会按原样进行解释,而clang和gcc的作者几乎肯定会否决任何建议立即添加此类语言。