严格别名和叠加inheritance

考虑以下代码示例:

#include  typedef struct AA; struct A { int x; int y; }; typedef struct BB; 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", bx, by, bz); } 

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

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

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

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

带有*ap =的行是严格的别名冲突:使用类型A的左值表达式写入类型B的对象。

假设该行不存在,我们移动到ap->x = 10; ap->y = 20; 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", bx); } 

对我来说,这在-O2输出214 ,在-O3输出2 ,用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

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

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

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