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

1.2k views Asked by At

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 obj(-2);
int absoluteVal = std::abs(A.constVar);

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

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

There are 3 answers

3
bolov On BEST ANSWER

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. 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?:

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. 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 (e.g. 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.

2
Jesper Juhl On

It would be perfectly legal for the compiler to generate

int absoluteVal = 2;

If abs has no side effects. It all hinges on "observable behaviour" (the as-if rule). If you cannot tell from the outside that the compiler made some transformation, then it is legal for the compiler to make that transformation.

2
Christophe On

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:

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.

This guarantees that non static const members (which are data members, even if they are const) are contained within the object. 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. (...)

So if your const member is not volatile nor atomic<>, the compiler can very well generate

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:

    ...
    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.