4

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?

  • 3
    With optimization enabled, those vars are fully optimized away by real-world compilers like GCC, clang, and MSVC. See for yourself on https://godbolt.org/ – Peter Cordes Jun 09 '23 at 20:59
  • 1
    Most likely they will. Whether or not the variables will actually exist or not does depend on what all you do with them. If you odr-use them for instance then a real variable needs to exist. – NathanOliver Jun 09 '23 at 20:59
  • Depends on the code following the function call. If the variables are not used after the function call, the variables can be replaced by their content, depending on the optimization level. The variable names may be kept if the option to keep symbols is given to the compiler. – Thomas Matthews Jun 09 '23 at 21:31
  • 1
    Does this answer your question? [Does compiler replace constant variables with its value while compiling C++](https://stackoverflow.com/questions/57079166/does-compiler-replace-constant-variables-with-its-value-while-compiling-c) – Satyam Sharma Jun 09 '23 at 21:37

1 Answers1

8

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 ints. Here's why, and how that works:

The as-if rule

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

- intro.abstract §1

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?

Compiler optimizations for your example

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

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96