The compiler isn't allowed to change observable behaviour except in the case of copy-elision. See other answers for details on how the ISO C++ standard allows changes to visible behaviour that way, and what rules govern implicit definition and use of constructors your code didn't define.
You need to look at the asm to see if it optimized out an object; in your case it will have inlined the constructor and just called std::cout::operator<<
to maintain the visible side-effects, while not actually reserving any storage space on the stack for your objects of type A
or B
. sizeof(A) == 1
but it's only padding, just because each object needs to have its own identity (and its own address). Even if there had been an int member = 42;
member in either A or B, since nothing ever reads it only an un-optimized debug build would actually reserve stack space for it and store a 42
.
In C++, every object has an address, including an int
. Except register
objects, but that's now deprecated and even removed. (In C++ (and C) terminology, even a primitive type is an object.) Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? shows an example of optimization or not.
We can see this just as well with local int
variables, no need to mess around with constructors.
int foo(int a){
int b = a+1;
int c = b*4;
int d = c >> 7;
return d;
}
In a debug build, every int
object in the C++ abstract machine gets its own address, and is actually stored and reloaded to stack space. Nobody wants that except for debugging (or if you do, use volatile
for that one object), so an optimized build for x86-64 looks like this, on the Godbolt compiler explorer
# GCC11.3 -O3
foo(int): # first arg in EDI per System V calling convention
lea eax, [4+rdi*4]
sar eax, 7
ret # return value in EAX
By contrast, with GCC with the default -O0
, and -fverbose-asm
to comment it with the names of C++ objects. I've added comments to the right of those. Intel syntax is operation dst, src
. Square brackets is an addressing mode, dereferencing a pointer in a register. (In this case just the frame pointer to access space in the stack frame of the current function.)
foo(int): # demangled asm symbol name
# prologue setting up RBP as a frame pointer
push rbp #
mov rbp, rsp #,
# It doesn't need to sub rsp, 24 because x86-64 SysV has a red zone below the stack pointer
mov DWORD PTR [rbp-20], edi # a, a # spill incoming register arg
mov eax, DWORD PTR [rbp-20] # tmp87, a # reload it
add eax, 1 # tmp86,
mov DWORD PTR [rbp-4], eax # b, tmp86 # int b = a+1;
mov eax, DWORD PTR [rbp-4] # tmp91, b
sal eax, 2 # tmp90,
mov DWORD PTR [rbp-8], eax # c, tmp90 # int c = b*4;
mov eax, DWORD PTR [rbp-8] # tmp95, c
sar eax, 7 # tmp94,
mov DWORD PTR [rbp-12], eax # d, tmp94 # int d = c >> 7;
mov eax, DWORD PTR [rbp-12] # _5, d # return d;
pop rbp # # epilogue
ret