6.5(p7)
有一个关于union
s和aggregate
s的项目符号:
对象的存储值只能由具有以下类型之一的左值表达式访问:
[...]
- 在其成员中包括上述类型之一的聚合或联合类型(递归地包括子聚合或包含的联合的成员),或者
这不是很清楚它的意思。它是否需要 * 至少一个成员 * 或 * 所有成员 * 来满足严格的别名规则。特别是关于union
s:
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);
}
DEMO
程序的行为是否定义良好?如果否,项目符号是什么意思?
2条答案
按热度按时间qyswt5oh1#
这意味着使用
union
是一种符合标准的方法,可以避免类型双关语和严格的别名冲突,如果试图通过不同类型的指针访问存储值,则会发生这种情况。以
unsigned
和float
为例,通常都是32位,在某些情况下可能需要查看unsigned*
或float*
中的存储值。在6.5(p7)之后,您可以在两种类型之间使用
union
,并访问与unsigned
或float
相同的信息,而无需类型双关指针或违反严格的别名规则。所以严格别名规则防止通过另一个类型的指针访问有效类型的内存,除非该指针是
char
类型或者指向两个类型之间的联合成员。(**注意:**标准的这一节绝不是一个清晰的例子。(你可以读10遍,仍然会挠头)它的意图是抑制指针类型的滥用,同时仍然认识到任何形式的内存块都必须能够通过字符类型访问,(
union
是其他允许的访问方式之一)。在过去的几年里,编译器在标记违反规则的行为方面做得更好。
00jrzges2#
要点有两个目的:首先,如果我们认识到,对一个左值的访问是或可能是基于一个特定类型的左值的,那么它应该被识别为后一种类型的左值或可能的左值,然后给出如下的内容:
一个从
u
派生出来的左值可以访问其中包含的所有对象。一个实现可以识别出一个左值基于另一个左值的情况是一个实现质量问题,一些高质量的编译器,比如icc,可以识别出这样的情况:对
load_array_element
的 * special * 调用可能对*array
执行的任何操作都将对u
执行(毕竟,它被赋予了直接从u
形成的左值的地址),而其他编译器(如Clang和gcc)甚至无法将*(u.x+i)
这样的结构识别为基于u
的左值。这个项目符号的第二个目的是建议,即使编译器太原始而无法跟踪直线代码中的左值派生,它也应该识别给定的声明:
如果它看到
*p=1; i=foo.x;
而没有注意到p
来自哪里,则它必须确保在读取foo.x
之前执行对*p
的写入。即使这仅在已经费心注意的编译器能够看到p
是从foo
形成的情况下才真正必要,与使对foo.x
的访问强制完成对整数指针的目标的任何未决写入相比,以那些术语描述事物将增加明显的编译器复杂性。注意,如果只对通过新派生指针访问结构体或联合体成员感兴趣,则不需要包含通过成员类型的左值访问结构体或联合体对象的一般权限。
foo.x = 1; p = &foo.x; i=*p;
,则获取foo.x
地址的操作应使编译器在运行可能使用该地址的任何代码之前完成对foo.x
的任何挂起写入(不知道下游代码将如何处理该地址的编译器可以简单地立即完成写入)。如果代码序列是foo.x = 1; i = *p;
,经由左值foo
访问foo.x
的动作将意味着可能标识该存储的任何现有指针将是“陈旧的”,因此编译器将没有义务识别这样的指针可能标识与foo.x
相同的存储。注意,尽管脚注88清楚地说明了“严格别名规则”的目的是指定何时允许对象别名,gcc和clang的解释将该规则解释为忽略对象由左值访问的情况的借口,而左值显然是从对象派生的。也许回想起来,标准的作者应该包括一个规定“请注意,这条规则并没有试图禁止低质量编译器以迟钝的方式行事,但也不是要鼓励这种行为”,但C89的作者没有理由期望这条规则会被解释为它所做的,而clang和gcc的作者几乎肯定会否决任何现在添加这种语言的建议。