通过工会别名

Aliasing through unions

6.5(p7) 有关于 unionaggregate 的项目符号:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

[...]

— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

这个不太明白是什么意思。它是否需要 至少一个成员 所有成员 来满足严格的别名规则。特别是 unions:

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

程序的行为是否定义明确?如果不是,项目符号是什么意思?

什么意思是使用 union 是一种符合标准的方法,可以避免类型双关和严格的别名冲突,如果您试图通过不同的指针访问存储的值,则会发生这种情况类型。

unsignedfloat 为例,通常都是 32 位,在某些情况下可能需要查看来自 unsigned*float* 的存储值.例如你不能做:

    float f = 3.3;
    // unsigned u = *(unsigned *)&f;  /* violation */

6.5(p7) 之后,您可以在两种类型之间使用 union 并访问与 unsignedfloat 相同的信息,而无需类型双关指针或 运行 违反了严格的别名规则,例如

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 是其他允许的访问方式之一。)

过去几年,编译器在标记违反规则方面做得更好。

要点有两个目的。首先,如果有人认识到对明显基于或可能基于特定类型左值的左值的访问应该被识别为后一种类型的左值或可能的左值,则给出如下内容:

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); }

特定的调用load_array_element可能用*array做的任何事情都会用u完成(它被赋予一个地址毕竟,直接由 u 形成的左值),而其他编译器(例如 clang 和 gcc)甚至无法将 *(u.x+i) 等构造识别为基于 u.[=34= 的左值]

项目符号的第二个目的是建议即使编译器太原始而无法跟踪直线代码中的左值推导,它也应该识别给定的声明:

int *p,i;
struct foo { int x;} foo;

如果它看到 *p=1; i=foo.x; 而没有注意 p 来自哪里,它必须确保在读取 [=23= 之前执行对 *p 的写入].即使只有在一直费心注意的编译器能够看到 p 是由 foo 形成的情况下才真正有必要,用这些术语描述事物也会增加与访问 foo.x 强制完成对整数指针目标的任何未决写入相比,明显的编译器复杂性。

请注意,如果只对通过新派生的指针访问结构或联合成员的情况感兴趣,则无需包括通过成员类型的左值访问结构或联合对象的一般权限。给定代码序列:foo.x = 1; p = &foo.x; i=*p;,获取 foo.x 地址的行为应该导致编译器在 运行 任何可能使用地址(不知道下游代码将如何处理该地址的编译器可以简单地立即完成写入)。如果代码序列是 foo.x = 1; i = *p;,通过左值 foo 访问 foo.x 的行为将意味着任何可能标识该存储的现有指针都是 "stale",因此编译器没有义务识别这样的指针可能标识与 foo.x.

相同的存储

请注意,尽管脚注 88 明确指出 "strict aliasing rule" 的目的是指定何时允许对象使用别名,但 gcc 和 clang 的解释将该规则解释为忽略以下情况的借口对象由从它们派生的左值访问。也许回想起来标准的作者应该包含一个条款 "Note that this rule makes no attempt to forbid low-quality compilers from behaving in obtuse fashion, but is not intended to invite such behavior" 但 C89 的作者没有理由期望该规则会被解释为它已经存在的,并且 clang 和 gcc 的作者几乎肯定会否决任何建议现在添加这样的语言。