严格的别名和覆盖继承

时间:2017-02-20 19:20:55

标签: c struct language-lawyer strict-aliasing

考虑以下代码示例:

#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 *对象是严格的别名违规。 但后来我意识到标准真的只需要:

  

对象的存储值只能由左值访问   具有以下类型之一的表达式:1)类型兼容   与对象的有效类型,(...)

ap->x这样的表达式确实有正确的类型和地址,而ap的类型在那里真的不重要(或者它是什么?)。在我看来,这意味着只要子结构不作为一个整体进行操作,这种类型的叠加继承就是正确的。

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

2 个答案:

答案 0 :(得分:10)

*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输出-O2,在2输出-O3 gcc。 在godbolt上为gcc 6.3生成的程序集是:

f:
    movl    (%rsi), %eax
    movl    $213, (%rdi)
    addl    $2, %eax
    movl    %eax, (%rsi)
    ret

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

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

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

答案 1 :(得分:1)

当编写C89时,如果没有支持结构指针,那么编译器必须维护联合的公共初始序列保证是不切实际的。相比之下,为结构指针指定CIS保证并不意味着如果不采用其地址,联合会表现出类似的行为。鉴于CIS保证自1974年1月以来一直适用于结构指针 - 甚至在union关键字被添加到语言之前 - 并且许多代码多年来一直依赖于这种行为在不可能合理的情况下涉及union类型的对象,并且C89的作者对使标准简洁而不是使其成为“语言 - 律师证明”更感兴趣,我建议C89根据工会规定CIS规则而不是结构指针几乎肯定是出于避免冗余的愿望,而不是希望允许编译器在应用CIS保证构造指针时自由地违反15年以上的先例。

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

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