如何通过良好的设计保留堆栈空间?

时间:2008-09-25 19:01:32

标签: c memory embedded stack rtos

我正在用C编程用于带有RTOS的RAM限制嵌入式微控制器。

我经常将我的代码分解为短函数,但每个函数调用都需要更多的堆栈内存。 每个任务都需要他的堆栈,这是项目中重要的内存消费者之一。

是否有替代方法可以使代码保持良好的组织性和可读性,仍保留内存?

10 个答案:

答案 0 :(得分:11)

尝试使调用堆栈更加平坦,因此调用调用a() b()的{​​{1}}调用c()而不是d()调用a() }},b()c()本身。

如果函数只被引用一次,请将其标记为d()(假设您的编译器支持此功能)。

答案 1 :(得分:10)

您的堆栈使用有3个组件:

  • 函数调用返回地址
  • 函数调用参数
  • 自动(本地)变量

最小化堆栈使用的关键是最小化参数传递和自动变量。实际函数调用本身的空间消耗相当小。

参数

解决参数问题的一种方法是传递结构(通过指针)而不是大量参数。


foo(int a, int b, int c, int d)
{
...
   bar(int a, int b);
}

改为:


struct my_params {
   int a;
   int b;
   int c;
   int d;
};
foo(struct my_params* p)
{
   ...
   bar(p);
};

如果你传递了很多参数,这个策略很好。如果参数都不同,那么它可能不适合你。你最终会传递一个包含许多不同参数的大型结构。

自动变量(本地人)

这往往是堆栈空间的最大消费者。

  • 阵列是杀手。不要在本地函数中定义数组!
  • 最大限度地减少局部变量的数量。
  • 使用必要的最小类型。
  • 如果重新入侵不是问题,您可以使用模块静态变量。

请记住,如果您只是将所有局部变量从本地范围移动到模块范围,则表示您没有保存任何空间。您为数据段空间交换了堆栈空间。

某些RTOS支持线程本地存储,它基于每个线程分配“全局”存储。这可能允许您在每个任务的基础上拥有多个独立的全局变量,但这会使您的代码不那么简单。

答案 2 :(得分:6)

如果您可以节省大量主内存但只有一小部分堆栈,我建议您评估静态分配。

在C中,在函数内声明的所有变量都是“自动管理”的,这意味着它们被分配在堆栈中。

将声明限定为“静态”将它们存储在主存储器而不是堆栈中。它们基本上表现得像全局变量,但仍然允许你避免过度使用全局变量带来的坏习惯。您可以将大型长寿命缓冲区/变量声明为静态以减少堆栈压力。

请注意,如果您的应用程序是多线程的或者使用递归,则这不能正常工作。

答案 3 :(得分:5)

启用优化,特别是积极的内联。编译器应该能够内联方法来最小化调用。根据您使用的编译器和优化开关,将某些方法标记为inline可能会有所帮助(或者可能会被忽略)。

使用GCC,尝试添加“-finline-functions”(或-O3)标志,并可能添加“-finline-limit = n”标志。

答案 4 :(得分:1)

为了评估嵌入式设置中代码的堆栈要求,我在某处阅读的一个技巧是在开始时使用已知模式填充堆栈空间(十六进制中的DEAD是我最喜欢的)并让系统运行而

正常运行后,读取堆栈空间并查看在运行过程中有多少堆栈空间未被替换。设计以留下至少150%的数据,以便处理可能尚未行使的所有可能的代码路径。

答案 5 :(得分:0)

你能用全局变量替换一些局部变量吗? 特别是阵列可能会堆积起来。

如果情况允许你在一些函数之间共享一些全局变量, 你有可能减少记忆足迹。

折衷成本增加了复杂性,并且功能之间产生不必要的副作用的风险与可能更小的内存占用量的风险更大。

您的职能有哪些变量? 我们谈论的是什么尺寸和限制?

答案 6 :(得分:0)

根据您的编译器以及优化选项的积极程度,您将为每个函数调用设置堆栈使用情况。因此,首先,您可能需要限制函数调用的深度。 一些编译器确实使用跳转而不是分支来实现简单的功能,这将减少堆栈的使用。显然,你可以通过使用汇编程序宏来跳转到函数而不是直接函数调用来做同样的事情。

正如其他答案中所提到的,内联是一种可用选项,尽管这样做的代价是更大的代码。

吃堆栈的另一个区域是本地参数。这个区域你有一些控制权。使用(文件级别)静态将避免以静态ram分配为代价的堆栈分配。全球同样。

在(真正的)极端情况下,您可以为使用固定数量的全局变量作为临时存储而不是堆栈中的本地变量的函数提出约定。棘手的一点就是确保不会同时调用任何使用相同全局变量的函数。 (因此惯例)

答案 7 :(得分:0)

如果你需要开始保留堆栈空间,你应该得到更好的编译器或更多的内存。

您的软件通常会增长(新功能,......),因此如果您必须通过考虑如何保留堆栈空间来启动项目,那么它从一开始就注定要失败。

答案 8 :(得分:0)

是的,RTOS可以真正占用RAM用于任务堆栈使用。我的经验是,作为RTOS的新用户,有使用更多任务的倾向。

对于使用RTOS的嵌入式系统,RAM可以是一种宝贵的商品。为了保留RAM,对于简单的功能,在一个任务中实现多个功能仍然是有效的,这些功能以循环方式运行,具有协作式多任务设计。从而减少任务总数。

答案 9 :(得分:0)

我想你可能在想象一个在这里不存在的问题。当大多数编译器在堆栈上“分配”自动变量时,它们实际上并没有做任何事情。

在执行“main()”之前分配堆栈。当您从函数a()调用函数b()时,紧接在a使用的最后一个变量之后的存储区域的地址被传递给b()。如果b()然后调用函数c()然后c的堆栈在b()定义的最后一个自动变量之后开始,这将成为b()的堆栈的开始。

请注意,堆栈内存已经存在并已分配,没有进行初始化,所涉及的唯一处理是传递堆栈指针。

这成为问题的唯一时间是所有三个函数都使用大量存储,然后堆栈必须占用所有三个函数的内存。尝试保留在调用堆栈底部分配大量存储的函数,即不要从它们调用另一个函数。

内存存储系统的另一个技巧是将函数的内存占用部分拆分为单独的自包含函数。