5

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

我添加了一些更改:

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

如您所见,我已将联合声明移到外部,因此它在 foo() 中可见。根据标准的评论,这应该使我的代码正确,但看起来严格的别名仍然会破坏 clang 3.4 和 gcc 4.8.2 的代码。

输出 -O0:

2

输出 -O2:

1

对于两个编译器。

所以我的问题是:

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

这对我来说似乎真的很糟糕,因为即使函数和联合都在同一个头文件中声明,这并不能保证联合在函数体的翻译单元中是可见的。

4

2 回答 2

3

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

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

于 2014-01-17T02:13:44.270 回答
0

编译器识别对聚合成员的访问是对聚合本身的访问的情况集合纯粹是实现质量问题,并且标准不努力识别使用非字符左值的任何情况表格aggregate.memberpointerToAggregate->member不会违反 6.5p7。一个至少不能处理定义的某些情况的编译器质量很低,以至于毫无用处,但标准没有努力禁止符合但无用的实现。

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

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

于 2018-06-13T20:12:13.243 回答