3

I expected that stack memory of scope-local variables is reused once they left the scope. This is true for GCC and MSVC, at least in optimised builds. In debug builds they don't reuse stack memory, I guess to be able to poison that memory and catch bugs.

But this is not true for Clang. E.g. the following code outputs different addresses:

#include <stdio.h>

int main()
{
    {
        int j;
        printf("%p\n", &j);
    }
    {
        int j;
        printf("%p\n", &j);
    }

    /*
    for (int i = 0; i != 2; ++i)
    {
        int j;
        printf("&j = %p\n", &j);
    }
    */
}

GodBolt

The same result (no reusage) if I replace int j; by int j[1];.

Interestingly, in cases where such behaviour has higher probability of overflowing the stack, e.g. if an array contains more than one element, its stack space is reused by Clang. Same for for loops (commented part of the code above).

It seems Clang does this deliberately. Why?

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
  • If you try to repeat the block 4 times, it starts to return the same address each time : https://godbolt.org/z/xfas6G. – François Andrieux Aug 26 '20 at 16:55
  • 1
    This can also happen in gcc. [In C++ block scope, is re-using stack memory the area of optimization?](https://stackoverflow.com/questions/63388369/in-c-block-scope-is-re-using-stack-memory-the-area-of-optimization). It depends on the optimization settings but also on the surrounding code. Different compilers have different optimizations strategies. – t.niese Aug 26 '20 at 16:55
  • 2
    You'll also find compilers allocating the entirety of a function's stack use at the beginning of the function. The variable won't be initialized until a block is entered, but storage for it may have already been claimed. Why keep juggling the stack pointer around if you don't have to? – user4581301 Aug 26 '20 at 17:00
  • Here is output of `gcc` : (1) Not using size optimisation : [Different addresses](https://wandbox.org/permlink/0TTywmpWVUhQegYf) (2) Using size optimisation : [Same address](https://wandbox.org/permlink/DLpjA4Mx4TUH7kEW). More appropriate flag : [`-fconserve-stack`](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html). I think that `clang` have similar (if not same) things (flags) also. – brc-dd Aug 26 '20 at 17:00
  • @t.niese any idea what benefits does this particular strategy provide? in optimised builds, where memory poisoning is not used – Andriy Tylychko Aug 26 '20 at 17:02
  • 4
    If I had to guess, its because you are using such a contrived example that uses two ints -- so the space is considered negligible. A larger object that is `2 * sizeof(int)` [will reuse the space](https://godbolt.org/z/TYETv9), just as 4 blocks of `int`s will. It's likely just the compiler's cost-benefit analysis determining that this isn't significant enough to care about. But I'm not a compiler author, so I can't speak authoritatively – Human-Compiler Aug 26 '20 at 17:09
  • Looks like this requires special handling in Clang implementation. They must have gone this extra mile for some reason. What benefits does this provide? – Andriy Tylychko Aug 26 '20 at 17:12
  • The benefit is to your build time. Sometimes the memory wasted is worth the extra time spent analyzing the code to try and minimize the memory footprint. Other times it isn't, so why bother? Just take the space of two ints and "suffer" the lose. – StoryTeller - Unslander Monica Aug 26 '20 at 18:01
  • Stack frames are normally aligned to 16 bytes, and aligned 16 bytes belong to the same cache line, so nothing is really "lost" space- and performance-wise and there is no need to bother with reusing every bit of stack memory. – rustyx Aug 26 '20 at 19:31
  • I know only the theory about how memory alignment is important for better performance. But not the details about specific hardware. So my guess is also, that for each portion of the stack that referes to one function (or creates another entity) the compiler will reserve memory so that it is alligned. And if you have one `int` you still have space for a second one one the stack. For this simple example it does not really matter if the memory is reused or if the other free part in the stack is used. – t.niese Aug 27 '20 at 07:57
  • For more complex examples there are so many things to consider, like branch prediction, pipline startup/teardown cost, memory alignment, … so it is hard to tell why a compiler chooses one strategy over the other without having an indepth knowledge about the CPU architecture and the compiler itself. – t.niese Aug 27 '20 at 07:59

0 Answers0