内联和堆栈帧控制

时间:2018-05-22 07:25:15

标签: c gcc embedded

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

仅以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
}

总是期望在测量点B,堆栈只包含buffer2并且buffer1已被释放?

除了函数调用(导致额外的堆栈使用)之外,还有什么办法可以很好地控制堆栈释放吗?

3 个答案:

答案 0 :(得分:5)

  

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

这里有很多混乱。 optimizing compiler只能在automatic variables中存储一些registers,而不使用调用框架中的任何插槽。 C语言规范(n1570)不需要任何调用堆栈。

并且调用帧中的给定寄存器或槽可以用于不同目的(例如,函数的不同部分中的不同自动变量)。 Register allocation是编译器的重要角色。

  

我是否总能指望在测量点B,堆栈只包含缓冲区2并且缓冲区1已被释放?

当然不是。编译器可以证明,在代码的某个稍后阶段,buffer1的空间不再有用,因此将该空间重用于其他目的。

  

有什么方法可以很好地控制堆栈解除分配吗?

,没有。 call stack是一个实现细节,可能不会被编译器和生成的代码使用(或者在您的观点中被滥用")。

对于一些愚蠢的例子,如果buffer1中没有使用foo,编译器可能不会为它分配空间。一些聪明的编译器可能只在其中分配8个字节,如果它们可以证明只有buffer1的8个第一个字节是有用的。

更严重的是,在某些案例中,GCC可以进行tail-call优化。

您应该对-fstack-reuse=all-Os感兴趣invoking GCC-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];
}

在Linux / Debian / x86-64上使用gcc -S -fverbose-asm -O2 e.c使用GCC8.1编译时在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

并且您看到堆栈帧增长100 * 4字节。情况仍然如此:

int f(int x, int y, int n) {
    int t[n];
    t[0] = x;
    t[1] = y;
    return t[0]+t[1];
}

实际上生成与上面相同的机器代码。如果不是上面的+而是调用某些inline int add(int u, int v) { return u+v; },则生成的代码不会更改。

请注意as-if rule以及undefined behavior的棘手概念(如果上面n为1,则为UB)。

答案 1 :(得分:4)

  

我是否总能指望在测量B时,堆栈只包含缓冲区2并且缓冲区1已被释放?

没有。它将取决于GCC版本,目标,优化级别,选项。

  

除了函数调用(导致额外的堆栈使用)之外,还有什么办法可以很好地控制堆栈释放吗?

您的要求是如此具体,我猜您可能必须自己编写汇编程序中的代码。

答案 2 :(得分:1)

mov BYTE PTR [rbp-20], 1mov BYTE PTR [rbp-10], 2仅显示堆栈帧中堆栈指针的相对偏移量。在考虑运行时情况时,它们具有相同的峰值堆栈使用率。

使用inline有两个不同之处: 1)在函数调用模式下,从foo()退出时将释放buffer1。但是在内联方法中,缓冲区1在从bar()退出之前不会被保留,这意味着峰值堆栈的使用将持续更长的时间。 2)函数调用会增加一些开销,例如保存堆栈帧信息,与内联模式进行比较