C ++ Can a persistent data class be optimized from the class by the compiler?
I know that constant variables outside of 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 the class is declared like this:
class A {
public:
const int constVar;
//other, modifiable variables
A(int val): constVar(val) {
//code to initialize modifiable variables
}
};
and I instantiate A and call a function like this:
A obj(-2);
int absoluteVal = std::abs(A.constVar);
was the compiler allowed to do this instead and make the class sizeof(int)
smaller ?:
A obj();
int absoluteVal = std::abs(-2);
source to share
The compiler can emit any code that preserves the "observable behavior" of the program (there is an exception to the copy constructor that can be removed even if it has observable behavior, but it doesn't apply here). It's called as if the rule
struct X { int x; };
auto foo()
{
X x{24};
return x.x;
}
any decent compiler will optimize the above:
foo(): # @foo()
mov eax, 24
ret
As you can see, this has nothing to do with constant (well, almost), just observable behavior. You can try to play with adding complex code and see how smart the compiler is when calculating, it can remove the code associated with the class instance. Hint: This is very smart.
It is not clear to me what you mean by this:
is a compiler to do this instead and make the sizeof (int) class smaller ?:
I can tell you that for a type X
and an object of X
that type, it is sizeof(x)
always = sizeof(X)
independent of class instances. In other words, the size of a class is determined when the class is defined, and as such it does not depend on possible instances or not. Class size is the sum of all non-static item sizes plus padding. A shim is an implementation defined and usually can be somewhat controlled (e.g. packed structures). Thus, no, the size of a class can never be less than the sum of the sizes of all non-static members without reference.
source to share
It would be perfectly legal for the compiler to generate
int absoluteVal = 2;
If abs
it has no side effects. It all depends on the "observable behavior" ( as-if rule ). If you cannot tell from the outside that the compiler has done some conversion, then the compiler must do that conversion.
source to share
Code optimization and object memory layout do not obey the same rules
The C ++ standard specifies the following object memory location :
1.8 / 2: Objects can contain other objects called sub-objects. A subobject can be a member subobject, a base class subobject, or an array element. (...)
9.2 / 13: Non-stationary data members of a (non-unit) class with the same access control are allocated so that later members have higher addresses within the class object. The distribution order of non-static data members with different access controls is undefined. Implementation alignment requirements may cause two adjacent members not to be allocated immediately after each other; so the space requirements for managing virtual functions and virtual base classes.
This ensures that non-static constant members (which are data members, even if they are constants) are contained within the object. Therefore, the compiler is not allowed to shrink the object size.
However, the compiler is free to perform code optimizations such as persistent propagation and removal of dead code, reordering, etc., as long as the observed behavior does not change:
1.9 / 5: A conforming implementation executing a well-formed program MUST provide the same observable behavior as one of the possible executions of a corresponding abstract machine instance with the same program and the same input. (...)
So, if your member is not volatile
and atomic<>
, the compiler can generate very well
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's an example in which the object cannot be optimized:
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 the results of the optimizer on the Internet : although the computation is absoluteVal
optimized, the object is created in 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-copied object to a stream requires every byte of the object to meet expectations.
source to share