以下是人为的例子.显然,编译器优化将极大地改变最终结果.但是,我不能强调这一点:通过暂时禁用优化,我打算在堆栈使用上有一个上限,可能,我希望进一步的编译器优化可以改善这种情况.
讨论仅以GCC为中心.我希望能够很好地控制自动变量从堆栈中释放的方式.使用块确定范围并不能确保在自动变量超出范围时释放内存.据我所知,功能可以确保这一点.
但是,在内联时,情况如何?例如:
inline __attribute__((always_inline)) void foo()
{
uint8_t buffer1[100];
// Stack Size Measurement A
// Do something
}
void bar()
{
foo();
uint8_t buffer2[100];
// Stack Size Measurement B
// Do something else
}
Run Code Online (Sandbox Code Playgroud)
我是否总能指望在测量点B,堆栈只包含buffer2并buffer1已被释放?
除了函数调用(导致额外的堆栈使用),有什么方法可以很好地控制堆栈解除分配?
我希望能够很好地控制自动变量从堆栈中释放的方式.
这里有很多混乱.的优化编译器可以存储一些自动变量仅在寄存器,而无需在呼叫帧使用任何时隙.C语言规范(n1570)不需要任何调用堆栈.
并且调用帧中的给定寄存器或槽可以被重用于不同目的(例如,函数的不同部分中的不同自动变量).寄存器分配是编译器的重要作用.
我是否总能指望在测量点B,堆栈只包含缓冲区2和缓冲区1?
当然不是.编译器可以证明在代码的某个稍后阶段,空间buffer1不再有用,因此将该空间重用于其他目的.
有没有办法可以很好地控制堆栈解除分配?
不,没有.所述调用栈是一个实现细节,并且可能不是由编译器和将所生成的代码中使用(或在你的观点是"滥用").
对于一些愚蠢的例子,如果buffer1没有使用foo,编译器可能不会为它分配空间.一些聪明的编译器可能只在其中分配8个字节,如果它们可以证明只有8个第一个字节buffer1是有用的.
更严重的是,在某些情况下,GCC能够进行尾部调用优化.
你应该有兴趣在调用GCC有-fstack-reuse=all,-Os,
-Wstack-usage=256, -fstack-usage,和其他选项.
当然,具体的堆栈使用取决于优化级别.您也可以检查生成的汇编程序代码,例如-S -O2 -fverbose-asm
例如,以下代码e.c:
int f(int x, int y) {
int t[100];
t[0] = x;
t[1] = y;
return t[0]+t[1];
}
Run Code Online (Sandbox Code Playgroud)
在Linux上使用/于Debian/x86-64的用GCC8.1编译时gcc -S -fverbose-asm -O2 e.c给出e.s
.text
.p2align 4,,15
.globl f
.type f, @function
f:
.LFB0:
.cfi_startproc
# e.c:5: return t[0]+t[1];
leal (%rdi,%rsi), %eax #, tmp90
# e.c:6: }
ret
.cfi_endproc
.LFE0:
.size f, .-f
Run Code Online (Sandbox Code Playgroud)
并且您看到堆栈帧不会增长100*4字节.这仍然是这样的:
int f(int x, int y, int n) {
int t[n];
t[0] = x;
t[1] = y;
return t[0]+t[1];
}
Run Code Online (Sandbox Code Playgroud)
实际上生成与上面相同的机器代码.如果不是+ 上面的话我会调用一些inline int add(int u, int v) { return u+v; }生成的代码不会改变.
要注意as-if规则,以及未定义行为的棘手概念(如果n上面是1,那么它是UB).