内联和堆栈帧控制

Jua*_*eni 5 c embedded gcc

以下是人为的例子.显然,编译器优化将极大地改变最终结果.但是,我不能强调这一点:通过暂时禁用优化,我打算在堆栈使用上有一个上限,可能,我希望进一步的编译器优化可以改善这种情况.

讨论仅以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,堆栈只包含buffer2buffer1已被释放?

除了函数调用(导致额外的堆栈使用),有什么方法可以很好地控制堆栈解除分配?

Bas*_*tch 5

我希望能够很好地控制自动变量从堆栈中释放的方式.

这里有很多混乱.的优化编译器可以存储一些自动变量仅在寄存器,而无需在呼叫帧使用任何时隙.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).