How does the compiler allocate memory for conditionally declared automatic variables in C++?

Advertisements

Say I have a function where depending on some runtime condition an expensive automatic object is created or a cheap automatic object is created:

void foo() {
   if (runtimeCondition) {
       int x = 0;
   } else {
       SuperLargeObject y;
   }
}

When the compiler allocates memory for a stack frame for this function, will it just allocate enough memory to store the SuperLargeObject, and if the condition leading to the int is true that extra memory will just be unused? Or will it allocate memory some other way?

>Solution :

It depends on your compiler and on the optimization settings. In unoptimized builds most C++ compilers will probably allocate stack memory for both objects and use one or the other based on which branch is taken. In optimized builds things get more interesting:

If both objects (the int and the SuperLargeObject are not used and the compiler can prove that constructing SuperLargeObject does not have side effects, both allocations will be elided.

If the objects escape the function, i.e. their addresses are passed to another function, the compiler has to provide memory for them. But since their lifetimes don’t overlap, they can be stored in overlapping memory regions. It is up to the compiler if that actually happens or not.

As you can see here, different compilers generate different code:

ICC

f(bool):
        sub       rsp, 408
        test      dil, dil
        lea       rax, QWORD PTR [400+rsp]
        lea       rdx, QWORD PTR [rsp]
        cmovne    rdx, rax
        mov       rdi, rdx
        call      escape(void const*)
        add       rsp, 408
        ret

ICC allocates enough memory two store both objects and then selects between the two non-overlapping regions based on the runtime condition (using cmov) and passes the selected pointer to the escaping function.

GCC

f(bool):
        sub     rsp, 408
        mov     rdi, rsp
        call    escape(void const*)
        add     rsp, 408
        ret

GCC is a bit smarter. It also allocates a region of 408 bytes, but passes the same pointer to escape and emits no cmov.

Clang

f(bool):
        sub     rsp, 408
        test    edi, edi
        lea     rdi, [rsp + 8]
        call    escape(void const*)@PLT
        add     rsp, 408
        ret

Clang does the same, but for reasons I don’t understand it still tests the condition.


You should also note, that when the compiler can place either or both of the variables in registers, no memory will be allocated at all. For int‘s and long‘s and other small objects that is most often the case, if their addresses to not escape the function.

Leave a ReplyCancel reply