C ++可以通过编译器在类外优化常量类数据吗?

时间:2017-07-31 14:05:32

标签: c++ compiler-optimization memory-layout object-layout

我知道类之外的常量变量可以直接优化到编译器的函数调用中,但编译器对常量类变量执行相同操作是否合法?

如果有一个类声明如下:

class A {
public:
const int constVar;
    //other, modifiable variables

A(int val): constVar(val) {
         //code to initialize modifiable variables

}
};

我创建一个A实例并调用这样的函数:

A obj(-2);
int absoluteVal = std::abs(A.constVar);

是否允许编译器执行此操作并使类sizeof(int)更小?:

A obj();
int absoluteVal = std::abs(-2);

3 个答案:

答案 0 :(得分:9)

编译器可以自由发出任何保留"可观察行为的代码。该程序(复制构造函数有一个例外,即使它有可观察的行为也可以省略,但它不适用于此)。这称为as if rule

struct X { int x; };

auto foo()
{
  X x{24};

  return x.x;
}

任何体面的编译器都会优化上述内容:

foo():                                # @foo()
        mov     eax, 24
        ret

正如你所看到的,它与constness(好吧,几乎)没有任何关系,只是与可观察的行为有关。您可以尝试使用代码添加复杂性,并了解编译器在确定它可以删除与类实例相关的代码时有多聪明。提示:它很聪明。

我不清楚你的意思是什么:

  

是允许编译器执行此操作并使类成为   sizeof(int)较小?:

我可以告诉您:对于类型X和类型x的对象sizeof(x)始终为= sizeof(X),无论该类的实例化如何。换句话说,类的大小是在定义类时确定的,因此它不受可能的实例化或缺乏的影响。类的大小是其非静态数据成员的所有大小加上填充的总和。填充是实现定义的,并且通常可以在某种程度上受到控制(例如,打包的结构)。所以不,类的大小永远不会小于所有非静态非参考数据成员的大小总和。

答案 1 :(得分:2)

编译器生成

是完全合法的
int absoluteVal = 2;

如果abs没有副作用。这一切都取决于"可观察到的行为" (as-if rule)。如果无法从外部告诉编译器进行了一些转换,那么编译器进行转换是合法的。

答案 2 :(得分:2)

代码优化和对象内存布局不遵守相同的规则

C ++标准规定了以下关于对象的内存布局

  

1.8 / 2:对象可以包含其他对象,称为子对象。子对象可以是成员子对象,基类子对象或   数组元素。 (...)

     

9.2 / 13:具有相同访问控制的(非联合)类的非静态数据成员已分配,以便以后的成员拥有更高的成员   类对象中的地址。非静态分配的顺序   具有不同访问控制的数据成员未指定。   实现对齐要求可能会导致两个相邻成员   不要在对方之后立即分配;可能   管理虚拟功能和虚拟基础的空间要求   类。

这保证了非静态const成员(即数据成员,即使它们是const)也包含在对象中。因此不允许编译器缩短对象的大小。

但是,只要不改变可观察行为,编译器就有权执行代码优化,例如常量传播和死代码消除,重新排序等。

  

1.9 / 5:执行格式良好的程序的符合实现应该产生与可能的一个相同的可观察行为   执行抽象机的相应实例   相同的程序和相同的输入。 (...)

因此,如果您的const成员不是volatile也不是atomic<>,那么编译器可以很好地生成

A obj();              // size not touched.  And const member will be initialized if needed
int absoluteVal = 2;  // constant propagation + inlining (the object is not even accessed)

其他信息

这是一个无法优化对象的示例:

A obj(-2);                                 // object is constructed
int absoluteVal = std::abs(obj.constVar);  // will be optimized a way into = 2 
std::cout<<absoluteVal<<std::endl;
size_t lo = sizeof(obj);
std::cout<<lo<<std::endl; 
std::cout.write((char*)&obj, lo);         // obj is written to a stream 
                                          // and output of content at &obj adress is observable behavior

你可以see online on the optimizer results:尽管absoluteVal的计算被优化掉,但是对象在其全长中被实例化并且其常量被初始化

    ...
    mov     esi, 2                      ; this is absoluteVal calculation
    mov     DWORD PTR [rsp+12], -2      ; the const in [rsp+12] object is nevertheless initialized
    ...
    lea     rsi, [rsp+12]               ; the address of the object 
    mov     edx, 4                      ; and its length 
    ...                                 ; are used to call cout.write()
    call    std::basic_ostream<char, std::char_traits<char> >::write(char const*, long) 

这是因为将这个简单的可复制对象写入流的可观察行为要求对象的每个字节都符合预期。