6.5.2.3结构和联合成员中C中严格别名规则的例外情况

6.5.2.3结构和联合成员中C中严格别名规则的例外情况,c,struct,unions,aliasing,C,Struct,Unions,Aliasing,引用C99标准: 6.5.2.3 5为了简化联合的使用,需要做出一项特殊保证:如果联合包含多个共享公共初始序列的结构(见下文),并且如果联合对象当前包含其中一个结构,允许在任何地方检查其中任何一个的公共初始部分,以确保可以看到联合的完整类型声明。如果对应成员具有一个或多个初始成员序列的兼容类型(对于位字段,宽度相同),则两个结构共享一个公共初始序列 这种情况有一个例子: // The following code is not a valid fragment because // the un

引用C99标准:

6.5.2.3

5为了简化联合的使用,需要做出一项特殊保证:如果联合包含多个共享公共初始序列的结构(见下文),并且如果联合对象当前包含其中一个结构,允许在任何地方检查其中任何一个的公共初始部分,以确保可以看到联合的完整类型声明。如果对应成员具有一个或多个初始成员序列的兼容类型(对于位字段,宽度相同),则两个结构共享一个公共初始序列

这种情况有一个例子:

// The following code is not a valid fragment because
// the union type is not visible within the function f.

struct t1 { int m; };
struct t2 { int m; };

int f(struct t1 *p1, struct t2 *p2)
{
    if (p1->m < 0)
        p2->m = -p2->m;
    return p1->m;
}

int g()
{
    union
    {
        struct t1 s1;
        struct t2 s2;
    } u;

    /* ... */
    return f(&u.s1, &u.s2);
}
//以下代码不是有效的片段,因为
//联合类型在函数f中不可见。
结构t1{int m;};
结构t2{int m;};
int f(结构t1*p1,结构t2*p2)
{
如果(p1->m<0)
p2->m=-p2->m;
返回p1->m;
}
int g()
{
联盟
{
结构t1-s1;
结构t2s2;
}u;
/* ... */
返回f(&u.s1和&u.s2);
}
我添加了一些更改:

#include <stdio.h>

struct t1 { int m; };
struct t2 { int m; };

union u
{
    struct t1 s1;
    struct t2 s2;
};

int foo(struct t1 *p1, struct t2 *p2)
{
    if (p1->m)
        p2->m = 2;
    return p1->m;
}

int main(void)
{
    union u u;
    u.s1.m = 1;
    printf("%d\n", foo(&u.s1, &u.s2));
}
#包括
结构t1{int m;};
结构t2{int m;};
联合大学
{
结构t1-s1;
结构t2s2;
};
int foo(结构t1*p1,结构t2*p2)
{
如果(p1->m)
p2->m=2;
返回p1->m;
}
内部主(空)
{
欧盟;
u、 s1.m=1;
printf(“%d\n”,foo(&u.s1,&u.s2));
}
正如您所看到的,我已经将union声明移到了外部,以便它在foo()中可见。根据standard的评论,这应该使我的代码正确,但看起来严格的别名仍然破坏了Clang3.4和GCC4.8.2的代码

具有-O0的输出:

二,

含-O2的输出:

一,

对于这两个编译器

所以我的问题是:

C是否真的依赖于联合声明来决定某些结构是否是严格别名规则的例外?或者gcc/clang都有bug


对我来说,这似乎真的很糟糕,因为即使函数和union都在同一个标题中声明,这也不能保证union在具有函数体的翻译单元中可见。

最重要的一点是,您的更改(向上移动union)根本不会更改函数的定义
foo
。它仍然是一个接收无关指针的函数。在您的示例中,传递的指针是相关的,而在其他地方可能不同。编译器的目标是服务于最一般的情况。功能主体在更改后有所不同,原因不明


您要问的问题是,对于某些命令行键,在特定编译器中实现的优化有多仔细。它与内存布局无关。在正确的编译器中,结果应该是相同的。编译器应处理两个不同指针实际上指向内存中同一位置的情况。

编译器认识到对聚合成员的访问就是对聚合本身的访问纯粹是一个实现质量问题,该标准也不试图识别使用非字符左值形式的
aggregate.member
pointerToAggregate->member
不会违反6.5p7的任何情况。一个编译器如果不能处理至少某些定义的情况,那么它的质量将非常低,以至于毫无用处,但是标准并没有禁止一致但毫无用处的实现

如果公共初始序列成员具有字符类型,则6.5p7将定义访问该成员的行为,而不管它是否是完整声明可见的联合的公共初始序列的成员。如果没有字符类型,则只有通过字符类型的左值或
memcpy
/
memmove
,或者在目标具有堆持续时间且用于读取的最终类型与用于写入的类型匹配的情况下,才会在6.5p7下定义访问


有许多迹象表明,高质量的编译器应该认识到,指向一种结构类型的指针可能用于访问另一种结构类型的CIS成员。无法识别任何其他指示的编译器可能会受益于将包含这两种类型的完整联合声明的存在视为此类指示。这样做可能会不必要地阻止一些其他方面有用的优化,但仍然会允许比完全禁用基于类型的别名分析更多的优化。

gcc 4.4.7的输出2在另一个线程中检查我的非答案,特别是GCC邮件列表讨论的链接:我真的希望看到这一点得到澄清。您首先给出的示例是:这是否超出标准?它看起来不像是标准所说的例子?我认为标准上说,如果在“main”中使用“u.s1.m=5”,那么编译器必须假设“u.s2.m”已经改变;但是如果你把指针传递给“u.s1”和“u.s2”,那就完全不同了;联合声明中存在两个结构应被视为通知编译器代码将覆盖这两种类型,因此公共初始序列的成员可能是别名。gcc的作者不喜欢该标准所说的内容,并选择忽略它,但我可以想象,如果这种可视性被认为不充分,那么标准就没有其他理由提及完整的类型是可见的。请注意,可能会出现别名。标准只提到联合,而不是其他任何东西。您的
foo
没有任何联合。。。这些都是不相关的。@ChronoKitsune:几乎所有Dennis Ritchie设计的C89之前的语言实现都允许程序员用一个共同的初始序列定义多个结构类型,并使用指向其中任何一个的指针来检查任何其他结构的成员。