C++ 编译器可以从 class 中优化常量 class 数据吗?

C++ Can constant class data be optimized out of class by compiler?

我知道 classes 之外的常量变量可以被编译器直接优化为函数调用,但是编译器对常量 class 变量做同样的事情是否合法?

如果有这样的class声明:

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);

是否允许编译器改为执行此操作并使 class 变得 sizeof(int) 更小?:

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

编译器生成

是完全合法的
int absoluteVal = 2;

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

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

struct X { int x; };

auto foo()
{
  X x{24};

  return x.x;
}

任何体面的编译器都会将上面的优化为:

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

如您所见,它与常量性(好吧,几乎)没有任何关系,只是与可观察到的行为有关。您可以尝试使用增加复杂性的代码,看看编译器在确定它可以删除与 class 实例相关的代码方面有多聪明。提示:它很聪明。


我不清楚你的意思:

is the compiler allowed to do this instead and make the class be sizeof(int) smaller?:

我可以告诉你:对于类型 X 和对象 x 这种类型 sizeof(x) 总是 = sizeof(X) 而不管 [=30 的实例化=].换句话说,class 的大小是在定义 class 时确定的,因此它不受可能的实例化或缺少实例化的影响。 class 的大小是其非静态数据成员的所有大小加上填充的总和。填充是实现定义的,通常可以在某种程度上进行控制(例如打包结构)。所以不,class 的大小永远不能小于所有非静态非引用数据成员的大小之和。

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

C++ 标准对对象的内存布局有如下规定

1.8/2: Objects can contain other objects, called subobjects. A subobject can be a member subobject, a base class subobject, or an array element. (...)

9.2/13: Nonstatic data members of a (non-union) class with the same access control are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions and virtual base classes.

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

但是,编译器有权执行代码优化,例如持续传播和死代码消除、重新排序等,只要可观察到的行为没有改变:

1.9/5: A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. (...)

所以如果你的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) 

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