【问题标题】:Reducing stack usage during recursion with GCC + ARM使用 GCC + ARM 减少递归期间的堆栈使用
【发布时间】:2012-10-20 18:17:35
【问题描述】:

我有一个用于嵌入式 ARM 处理器的递归下降解析器(在 C + GCC 中,用于 ARM Cortex M3)。

在运行它时,我注意到它使用了大量的堆栈空间(甚至比您预期的还要多),经过仔细检查,我发现正在发生这种情况:

extern int bar(int *p);

int foo() {
 int z = foo(); // it's an example!

 int n[100];  // stack usage
 return z+bar(n); // calling bar(n) stops n from being optimised out
}

运行结果arm-none-eabi-gcc -fomit-frame-pointer -S test.c

foo:
    str lr, [sp, #-4]!  ; Push link register
    sub sp, sp, #412    ; Reserve space on stack, even if we don't need it now!
    bl  foo             ; Recurse
    str r0, [sp, #404]  ; Store result
    ...

所以在函数开始时,它将整个堆栈帧压入堆栈。然而,经过几次迭代后,堆栈中出现了大量尚未使用的东西。

理想情况下,我希望 GCC 生成:

foo:
    str lr, [sp, #-4]!  ; Push link register
    ; Don't reserve space, because we don't need it
    bl  foo             ; Recurse
    sub sp, sp, #412    ; Reserve space now
    str r0, [sp, #404]  ; Store result
    ...

(这可能是不正确的,但我希望你明白)

使用下面的代码可以实现有点像这样的事情,但它真的很讨厌(如果 GCC 内联 fooworker,它会再次中断!)。一定有更好的办法吗?

int fooworker(int z) {
 int n[100];  // stack usage
 return z+bar(n); // calling bar(n) stops n from being optimised out
}


int foo() {
 return fooworker(foo());
}

那么有没有办法告诉 GCC 只在基本块的开头扩大堆栈,或者是否有一个“障碍”语句导致在该点添加额外的推送/弹出操作?我猜 GCC 正在使用一种 ARM 标准调用类型 - 但是有没有办法用另一种调用类型来标记这些函数,这种调用类型对堆栈来说更有效,或者有没有办法重写函数使得堆栈是用得更理智一点?

请不要告诉我不要使用递归,它没有回答问题。

【问题讨论】:

  • 我现在尝试了这个功能,它只是保存了 r7 和 lr,但我使用的是不同的工具链,我停止使用 codeourcy 有太多问题
  • 对不起,我稍微改变了问题(我做了一个更好的代码示例)-您使用了最新的代码吗?经过一些测试,我认为大多数 GCC 版本都会做同样的事情。
  • Aniket:我更改了示例,但尾递归不是问题末尾代码中正在执行的操作吗?理想情况下,GCC 将能够在函数中拥有两个不同的堆栈帧
  • 虽然这显然是次优代码,并且延迟在堆栈上分配空间很好,但我想知道对于非平凡的示例是否检测到它不是非常困难?

标签: c memory gcc stack arm


【解决方案1】:
int *n = alloca(sizeof(*n) * 100);

这很难看,我个人将函数分成两部分,但似乎在我的 gcc on amd64 上的所有优化级别都有效。

【讨论】:

  • 谢谢,我没想到。然而实际上,我的函数使用的堆栈空间是由在不同时间使用的许多较小的变量组成的,所以我认为 alloca 太难了。通过拆分功能,您的意思是像我在问题的底部提出的那样,还是有更好的方法?
  • @GordonWilliams 你在底部的做法。解决您对使用编译器标志进行内联的担忧很可能比希望控制 gcc 如何分配堆栈的编译器标志更容易。
  • 请注意:您可以强制不使用 '__attribute((noinline))' 进行内联,而 GCC 的 -fverbose-asm 命令行选项实际上为您提供了每个函数的堆栈帧大小 -您可以对其进行 grep 和排序,以了解哪些函数占用了最多的堆栈空间。
【解决方案2】:

这都容易被优化,但你也可以尝试引入一个新的范围:

extern int bar(int *p);

int foo() {
  int z = foo();

  {
    int n[100];
    return z+bar(n);
  }
}

新作用域的引入意味着n 不应该在foo() 被调用之前存在。同样,优化可以打破这一切,就像您自己的解决方案或接受的解决方案一样。

【讨论】:

  • 不幸的是,这不起作用,因为 gcc 在堆栈帧中为函数开头的所有变量分配空间,包括嵌套范围内的变量。
猜你喜欢
  • 2017-04-09
  • 1970-01-01
  • 2011-08-22
  • 1970-01-01
  • 2014-12-10
  • 2016-07-15
  • 2020-09-23
  • 2015-02-08
  • 1970-01-01
相关资源
最近更新 更多