C ++: How does the compiler know how much memory is allocated for each stack of frames?

The first answer here mentioned the following about stack memory in C ++:

When a function is called, the block is reserved at the top of the stack for local variables and some credentials.

This makes sense at the top level, and I wonder how the smart compilers when allocating this memory on their own, given the context of this question : Since the curly braces themselves are not a stack frame in C (I assume this is true for C ++ as well) , I want to check if compilers optimize reserved memory based on variable regions within the same function.

In the following case, I assume that the stack looks like before the function call:

-------- |main()| -------- <- stack pointer: space above it is used for current scope | | | | | | | | -------- 

And then after calling the f() function:

 -------- |main()| -------- <- old stack pointer (osp) | f() | -------- <- stack pointer, variables will now be placed between here and osp upon reaching their declarations | | | | | | | | -------- 

For example, given this function

 void f() { int x = 0; int y = 5; int z = x + y; } 

Presumably, this will just allocate 3*sizeof(int) + extra overhead for bookkeeping.

However, what about this function:

 void g() { for (int i = 0; i < 100000; i++) { int x = 0; } { MyObject myObject[1000]; } { MyObject myObject[1000]; } } 

Ignoring compiler optimizations, which can push out a lot of things from the above, since they actually do nothing, I am interested in the following example in the second example:

  • For the for loop: will the stack space be large enough to fit all 100,000 ints?
  • Also, will the stack space contain 1000*sizeof(MyObject) or 2000*sizeof(MyObject) ?

In general: does the compiler take the variable region into account when determining how much memory is needed for a new stack frame before calling a specific function? If it depends on the compiler, how do some well-known compilers do it?

+5
source share
2 answers

The compiler will allocate space as needed (usually for all elements at the beginning of the function), but not for each iteration in the loop.

For example, what Clang produces like LLVM-IR

 define void @_Z1gv() #0 { %i = alloca i32, align 4 %x = alloca i32, align 4 %myObject = alloca [1000 x %class.MyObject], align 16 %myObject1 = alloca [1000 x %class.MyObject], align 16 store i32 0, i32* %i, align 4 br label %1 ; <label>:1: ; preds = %5, %0 %2 = load i32, i32* %i, align 4 %3 = icmp slt i32 %2, 100000 br i1 %3, label %4, label %8 ; <label>:4: ; preds = %1 store i32 0, i32* %x, align 4 br label %5 ; <label>:5: ; preds = %4 %6 = load i32, i32* %i, align 4 %7 = add nsw i32 %6, 1 store i32 %7, i32* %i, align 4 br label %1 ; <label>:8: ; preds = %1 ret void } 

This is the result:

 class MyObject { public: int x, y; }; void g() { for (int i = 0; i < 100000; i++) { int x = 0; } { MyObject myObject[1000]; } { MyObject myObject[1000]; } } 

So, as you can see, x is allocated only once, not 100,000 times. Because only ONE of these variables will exist at any given time.

(The compiler can reuse the space for myObject[1000] for x and the second myObject[1000] - and will probably do it for an optimized build, but in this case it will also completely remove these variables since they are not, so it will not be displayed very good)

+4
source

In a modern compiler, a function is first converted to a flow graph. In each arc of the stream, the compiler knows how many variables in real time, i.e. Holding the visible value. Some of them will live in registries, and for the rest, the compiler will need to reserve stack space.

Things get a little more complicated as the optimizer gets further involved because he may prefer not to move stack variables. It is not free.

However, at the end the compiler is ready for assembly operations and can simply count how many unique stack addresses are being used.

+2
source

Source: https://habr.com/ru/post/1245479/


All Articles