严格的别名和覆盖继承

Strict aliasing and overlay inheritance

考虑这个代码示例:

#include <stdio.h>

typedef struct A A;

struct A {
   int x;
   int y;
};

typedef struct B B;

struct B {
   int x;
   int y;
   int z;
};

int main()
{
    B b = {1,2,3};
    A *ap = (A*)&b;

    *ap = (A){100,200};      //a clear http://port70.net/~nsz/c/c11/n1570.html#6.5p7 violation

    ap->x = 10;  ap->y = 20; //lvalues of types int and int at the right addrresses, ergo correct ?

    printf("%d %d %d\n", b.x, b.y, b.z);
}

我曾经认为将 B* 转换为 A* 并使用 A* 操纵 B* 对象是严格的别名违规行为。 但后来我意识到标准真的只需要:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types: 1) a type compatible with the effective type of the object, (...)

ap->x 等表达式确实具有正确的类型和地址,而 ap 的类型在那里应该无关紧要(或者是吗?)。在我看来,这意味着只要子结构不作为一个整体进行操作,这种类型的覆盖继承就是正确的。

这种解释是否有缺陷或表面上与标准作者的意图不一致?

带有 *ap = 的行是一个严格的别名违规:类型 B 的对象是使用类型 A 的左值表达式编写的。

假设那条线不存在,我们转到 ap->x = 10; ap->y = 20;。在这种情况下,int 类型的左值用于写入 int.

类型的对象

对于这是否是严格的别名违规存在分歧。我认为标准的字母说它不是,但其他人(包括 gcc 和 clang 开发人员)认为 ap->x 暗示 *ap 被访问。大多数人同意标准对严格别名的定义过于模糊,需要改进。

使用您的结构定义的示例代码:

void f(A* ap, B* bp)
{
  ap->x = 213;
  ++bp->x;
  ap->x = 213;
  ++bp->x;
}

int main()
{
   B b = { 0 };
   f( (A *)&b, &b );
   printf("%d\n", b.x);
}

对我来说,这个输出 214-O22-O3 ,gcc。 在 godbolt 上为 gcc 6.3 生成的程序集是:

f:
    movl    (%rsi), %eax
    movl    3, (%rdi)
    addl    , %eax
    movl    %eax, (%rsi)
    ret

这表明编译器已将函数重新排列为:

int temp = bp->x + 2;
ap->x = 213;
bp->x = temp;

因此编译器必须考虑 ap->x 可能不会别名 bp->x.

编写 C89 时,如果编译器不支持联合的公共初始序列保证, 不支持它们用于结构指针,那将是不切实际的。相比之下,为结构指针指定 CIS 保证并不意味着联合会在其地址未被占用时表现出类似的行为。鉴于 CIS 保证自 1974 年 1 月以来一直适用于结构指针——甚至在 union 关键字被添加到该语言之前——并且许多代码多年来一直依赖于这种行为涉及 union 类型的对象,并且 C89 的作者更感兴趣的是使标准简洁而不是使它 "language-lawyer-proof",我建议 C89 的 CIS 规范以联合而不是struct pointers 几乎可以肯定是出于避免冗余的愿望,而不是希望允许编译器自由地违反 15 年以上将 CIS 保证应用于结构指针的先例。

C99 的作者认识到,在某些情况下,将 CIS 规则应用于结构指针可能会损害原本有用的优化,并指定如果一种结构类型的指针用于检查另一种结构类型的成员的 CIS ,除非包含两个结构的 complete 联合类型的定义在范围内,否则 CIS 保证将不成立。因此,为了使您的示例与 C99 兼容,它需要包含一个包含两个结构的 union 类型的定义。该规则的动机似乎是希望允许编译器将 CIS 的应用限制在他们有理由期望两种类型可能以相关方式使用的情况下,并允许代码指示类型相关而无需为此目的添加新的语言结构。

gcc 的作者似乎认为,因为代码接收一个指向联合成员的指针然后想要访问联合的另一个成员是不寻常的,所以仅仅一个完整的联合类型的可见性定义不应该足以强制编译器支持 CIS 保证,即使 CIS 的大多数使用总是围绕结构指针而不是联合。因此,即使在 C99 标准要求的情况下,gcc 的作者也拒绝支持像您这样的结构。