Say you have a function
void func(int a, int b, int c, int d);
Now, when you call this function, because it has a lot of parameters, instead calling it like this:
func(1, 2, 3, 4);
you might call it like this:
const int a = 1;
const int b = 2;
const int c = 3;
const int d = 4;
func(a, b, c, d);
to better understand what each value is.
So the question is: do C++ compilers just replace the second way with the first or does it actually allocate space for those filler variables in the stack, wasting both memory and time?
Compilers are allowed to optimize -and will try to optimize with the right flags- your code as much as possible. This includes eliminating your const int
s. Here's why, and how that works:
[...] Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.6 [...]
6) This provision is sometimes called the “as-if” rule, because an implementation is free to disregard any requirement of this document as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. [...]
Or in plain words: if your code's behavior is the unchanged, the compiler can do anything it wants with it to make it faster.
Also see: What exactly is the "as-if" rule?
const int a = 1;
const int b = 2;
const int c = 3;
const int d = 4;
func(a, b, c, d);
Each of the four variables declares an object, and objects occupy memory.
Intuition would tells us that the stack needs to be 4 * sizeof(int)
bytes larger to store all of them.
However, we're not using any of these objects' addresses, and we're just calling a function with them, so the following code:
void func(int a, int b, int c, int d);
void foo() {
const int a = 1;
const int b = 2;
const int c = 3;
const int d = 4;
func(a, b, c, d);
}
compiles to: (see CE example using GCC 13 with -O2
)
foo():
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
jmp func(int, int, int, int)
Note: with fewer or no optimizations (e.g. -O0
), you would get much different assembly, with all four int
s residing on the stack.
We get the same assembly as if we had written:
func(1, 2, 3, 4);
So it's as if we never declared these objects and never occupied memory, but simply used temporary objects 1
, 2
, 3
, 4
that have no address.
This optimization is allowed, because the observable behaviour is still the same: We're still calling func
with the same arguments (passed by register, because of our calling conventions).
In general, I recommend using Compiler Explorer to see what kind assembly the compiler generates. It can help you understand many optimizations (or missed optimizations).