为什么GCC和Clang不进行混叠优化?

Joh*_*itb 27 c++ optimization gcc strict-aliasing compiler-optimization

我有一个案例,朋友将类型为"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;
}
Run Code Online (Sandbox Code Playgroud)

通过严格的别名分析,GCC(也是Clang)总是返回10,而不是11,因为b永远不会指向a明确定义的代码.但是,如果我删除B::x(因为实际上是在我朋友的代码的情况下),GCC的输出汇编代码并没有优化的回归访问a.a并重新加载从内存中值.因此我的朋友的代码g在GCC(按照他的意图)调用"有效",即使我认为它仍然有未定义的行为

g((B*)&a);
Run Code Online (Sandbox Code Playgroud)

因此,在基本相同的两种情况下,GCC优化了一种情况并且没有优化另一种情况.是因为b可以合法地指出a?或者是因为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;
}
Run Code Online (Sandbox Code Playgroud)

汇编器输出g的回报1,不0,即使AB是布局兼容(7.2p8).


所以我的另一个问题是(引用一个答案):"两个具有完全相同布局的类可能被认为"几乎相同"并且它们被排除在优化之外." .有人可以为GCC或Clang提供此证明吗?

Ben*_*igt 8

如果删除B::x,则B符合9p7中对标准布局类的要求,并且访问变得非常明确,因为这两种类型是布局兼容的,9.2p17和成员都具有相同的类型.


标准布局类是一个类:

  • 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  • 没有虚函数(10.3),也没有虚基类(10.1),
  • 对所有非静态数据成员具有相同的访问控制(第11条),
  • 没有非标准布局基类,
  • 或者在最派生类中没有非静态数据成员,并且最多只有一个具有非静态数据成员的基类,或者没有具有非静态数据成员的基类,并且
  • 没有与第一个非静态数据成员相同类型的基类.

如果两个标准布局结构类型具有相同数量的非静态数据成员,则它们是布局兼容的,并且相应的非静态数据成员(按声明顺序)具有布局兼容类型.

  • @BenVoigt请耐心等待,但我仍然不明白为什么在课堂上`B`和'A`没有别名冲突.显然你是说别名分析不检查匹配的类类型,而只检查被访问的成员.但如果是这样的话,为什么*做*子弹列表类?它说"一个类型是(可能是cv限定的)基类类型的对象的动态类型" - 这显然涉及访问中的类类型.或者你是说'ab`不构成通过`decltype(a)`类型的左值来访问对象的值? (2认同)
  • 严格别名规则没有布局兼容类的例外,所以我不清楚你认为这是如何回答问题的.建议在答案中编辑更多解释. (2认同)

Swi*_*Pie 6

未定义的行为包括它确实有效的情况,即使它不应该.

根据此联合的标准用法,允许访问标头或数据成员的类型和大小字段:

union Packet {
   struct Header {
   short type;
   short size;  
   } header;
   struct Data {
   short type;
   short size;  
   unsigned char data[MAX_DATA_SIZE];
   } data;
}
Run Code Online (Sandbox Code Playgroud)

这严格限于工会,但许多编译器支持这种扩展,只要"不完整"类型将以未定义大小的数组结束.如果从子类中删除额外的静态非成员,它确实变得微不足道并且布局兼容,这允许别名?

struct A {
  int a;
};

struct B  {
  int a;
  //int x;
};

A a;

int g(B *b) {
   a.a = 10;
   b->a++;
   return a.a;
}
Run Code Online (Sandbox Code Playgroud)

但仍然执行别名优化.在具有相同数量的非静态成员的情况下,假定最派生的类与基类相同.让我们颠倒顺序:

#include <vector>
#include <iostream>

struct A {
  int a;
};

struct B : A  {
  int x;
};

B a;

int g(A *b) {
   a.a = 10;
   b->a++;
   return a.a;
}

int main()
{
    std::cout << g((A*)&a);
}
Run Code Online (Sandbox Code Playgroud)

这与预期一样返回11,因为B显然也是A,与原始尝试不同.让我们进一步发挥

struct A {
  int a;
};

struct B : A {
    int  foo() { return a;}
};
Run Code Online (Sandbox Code Playgroud)

除非foo()是虚拟的,否则不会导致别名优化.将非静态或const成员添加到B将导致"10"答案,添加非平凡的构造函数或静态不会.

PS.在第二个例子中

enum A : int { X, Y };
enum B : int { Z };
Run Code Online (Sandbox Code Playgroud)

这两者之间的布局兼容性由C++ 14定义,它们与底层类型(但可转换)不兼容.有点像

 enum A a = Y;
 enum B b = (B*)a;
Run Code Online (Sandbox Code Playgroud)

可能会产生未定义的行为,就像您尝试使用任意32位值映射float一样.