【发布时间】:2013-06-16 03:03:54
【问题描述】:
I have a case 朋友将“Base”类型的非基类对象强制转换为“Derived”类类型对象,其中“Derived”是“Base”的派生类,只添加函数,不添加数据.在下面的代码中,我确实在派生类中添加了一个数据成员x
struct A {
int a;
};
struct B : A {
// int x;
int x;
};
A a;
int g(B *b) {
a.a = 10;
b->a++;
return a.a;
}
通过严格的别名分析,GCC(也是 Clang)总是返回 10,而不是 11,因为在定义明确的代码中,b 永远不能指向 a。但是,如果我删除B::x(实际上是我朋友的代码中的情况),GCC 的输出汇编代码不会不优化a.a 的返回访问并从内存中重新加载值。所以我朋友的代码在 GCC 上调用 g“工作”(如他所愿),即使我认为它仍然具有未定义的行为
g((B*)&a);
所以在本质上相同的两种情况下,GCC 优化了一种情况而没有优化另一种情况。 是不是因为b 可以合法地 指向@ 987654333@?还是因为 GCC 只是不想破坏真实世界的代码?
我测试了陈述的答案
如果您删除 B::x,则 B 满足 9p7 中标准布局类的要求,并且访问变得完美定义,因为这两种类型是布局兼容的,9.2p17。
具有两个布局兼容的枚举
enum A : int { X, Y };
enum B : int { Z };
A a;
int g(B *b) {
a = Y;
*b = Z;
return a;
}
g 的汇编器输出返回 1,而不是 0,即使 A 和 B 是布局兼容的 (7.2p8)。
所以我的进一步问题是(引用一个答案):“具有完全相同布局的两个类可能被认为是“几乎相同”,它们被排除在优化之外。” 。 有人可以为 GCC 或 Clang 提供证明吗?
【问题讨论】:
-
@MatsPetersson g 被称为
g((B*)&a)。在我的测试 sn-p 中,它没有被调用(我只需要g的汇编器输出) -
您的问题应直接发送至 GCC 邮件列表。为什么某些实现会在未定义的行为情况下这样做?因为实现的细节通过一些内部表示加起来。此外,如果这是一个 GCC 主义,它可能会通过使用
-std=c++11而不是-std=gnu++11而消失。 -
当出现关于未定义行为的问题时,我不喜欢某些人的心态。未定义的行为并不意味着编译器开发人员关闭他们的大脑并玩骰子。这也不意味着他们不在乎。
-
我同意,我发现了 3.10p10 - 不幸的是,我的图形驱动程序决定停止工作。我同意,未定义只是意味着由编译器供应商做一些尽可能有意义的事情——但有些情况很难做到,编译器要“意识到” aa 和 b->a 是同样在这里[或没有意识到,但要安全行事],他们将不得不不必要地存储和加载数据,以防万一您的
b对象不应该是指向a对象的指针实际上是同样a.a。人们更喜欢编写正确的快速代码。 -
我在 GCC、clang、ICC 上对其进行了测试,都产生了相同的程序集,如 JohannesSchaub-litb 所述。我还尝试了这些编译器的不同版本。他们3个都同意。如果
B没有声明任何非静态数据成员或任何虚函数,则编译器假定*b可以是a的别名。也许早期的优化会替换不添加任何数据(包括 vptr)的派生类?
标签: c++ gcc optimization compiler-optimization strict-aliasing