考虑下面的代码示例:
#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 *对象之类的行为是严格的别名冲突。
但是后来我意识到该标准实际上只要求:
和诸如
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);
}
对我来说,它使用gcc在
214
处输出-O2
,在2
处输出-O3
。针对gcc 6.3在godbolt上生成的程序集为:
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
。关于c - 严格的别名和覆盖继承,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42352681/