简体   繁体   English

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

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

I know that constant variables outside classes can be optimized directly into function calls by the compiler, but is it legal for the compiler to do the same for constant class variables? 我知道类之外的常量变量可以直接优化到编译器的函数调用中,但编译器对常量类变量执行相同操作是否合法?

If there is a class declared like this: 如果有一个类声明如下:

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

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

}
};

and I create an instance of A and call a function like this: 我创建一个A的实例并调用这样的函数:

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

is the compiler allowed to do this instead and make the class be sizeof(int) smaller?: 是否允许编译器执行此操作并使类的sizeof(int)更小?:

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

The compiler is free to emit any code that preserves the "observable behavior" of the program (there is an exception with copy constructor which can be elided even if it has observable behavior, but it doesn't apply here). 编译器可以自由地发出任何保留程序“可观察行为”的代码(复制构造函数有一个例外,即使它有可观察的行为也可以省略,但这里不适用)。 This is called the as if rule 这称为规则

struct X { int x; };

auto foo()
{
  X x{24};

  return x.x;
}

any decent compiler will optimize the above to this: 任何体面的编译器都会优化以上内容:

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

As you can see, it doesn't have anything to do with constness (well, almost), just with observable behavior. 正如你所看到的,它与constness(好吧,差不多)没有任何关系,只是与可观察的行为有关。 You can try and play with the code adding in complexity and see how smart is the compiler in figuring out it can remove the code related to the class instance. 您可以尝试使用代码添加复杂性,并了解编译器在确定它可以删除与类实例相关的代码时有多聪明。 Hint: it is very smart. 提示:它很聪明。


Is not clear to me what you mean by this: 我不清楚你的意思是:

is the compiler allowed to do this instead and make the class be sizeof(int) smaller?: 是否允许编译器执行此操作并使类的sizeof(int)更小?:

I can tell you that: for a type X and an object x of such type sizeof(x) is always = sizeof(X) regardless of instantiations of the class. 我可以告诉你:对于类型X和类型sizeof(x)的对象x总是= sizeof(X)无论类的实例化如何。 In other words the size of the class is determined when the class is defined, and as such it is not influenced by possible instantiations or lack of. 换句话说,类的大小是在定义类时确定的,因此它不受可能的实例化或缺乏的影响。 The size of a class is the sum of all the sizes of its non-static data members plus padding. 类的大小是其非静态数据成员的所有大小加上填充的总和。 The padding is implementation defined and can be usually be somewhat controlled (eg packed structs). 填充是实现定义的并且通常可以在某种程度上受到控制(例如,打包的结构)。 So no, the size of a class can never be smaller than the sum of the sizes all it's non-static non-reference data members. 所以不,类的大小永远不会小于所有非静态非引用数据成员的大小总和。

It would be perfectly legal for the compiler to generate 编译器生成是完全合法的

int absoluteVal = 2;

If abs has no side effects. 如果abs没有副作用。 It all hinges on "observable behaviour" (the as-if rule ). 这一切都取决于“可观察的行为”( as-if规则 )。 If you cannot tell from the outside that the compiler made some transformation, then it is legal for the compiler to make that transformation. 如果您无法从外部告诉编译器进行了一些转换,那么编译器进行转换是合法的。

Code optimizing and object memory layout don't obey the same rules 代码优化和对象内存布局不遵守相同的规则

The C++ standard states the following about the memory layout of the objects : C ++标准规定了关于对象内存布局的以下内容:

1.8/2: Objects can contain other objects, called subobjects. 1.8 / 2:对象可以包含其他对象,称为子对象。 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. 9.2 / 13: 分配具有相同访问控制的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。 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. 因此可能需要空间来管理虚拟功能和虚拟基类。

This guarantees that non static const members (which are data members, even if they are const) are contained within the object. 这保证了非静态const成员(即数据成员,即使它们是const)也包含在对象中。 So the compiler is not allowed to shorten the size of an object. 因此不允许编译器缩短对象的大小。

However, the compiler is authorized to perform code optimization such as constant propagation and dead code elimination, reordering, etc. as long as the observable behavior is not altered: 但是,只要不改变可观察行为,编译器就有权执行代码优化,例如常量传播和死代码消除,重新排序等。

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. 1.9 / 5:执行格式良好的程序的符合实现应该产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。 (...) (......)

So if your const member is not volatile nor atomic<> , the compiler can very well generate 因此,如果你的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)

Additional information 附加信息

Here an example where the object can't be optimized away : 这里是一个无法优化对象的示例:

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

You can see online on the optimizer results : despite the computation of absoluteVal being optimized away, the object is instantiated in its full length and its constant is initialized : 您可以在线查看优化器结果 :尽管优化了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) 

This is because the observable behavior of writing this trivially copyable object to a stream requires every byte of the object to fit to the expectations. 这是因为将这个简单的可复制对象写入流的可观察行为要求对象的每个字节都符合期望。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM