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

发布于 2024-07-05 09:15:54 字数 164 浏览 9 评论 0原文

我正在用 C 语言为带有 RTOS 的 RAM 有限的嵌入式微控制器进行编程。

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

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

I'm programming in C for RAM limited embedded microcontroller with RTOS.

I regularly break my code to short functions, but every function calling require to more stack memory.
Every task needs his stack, and this is one of the significant memory consumers in the project.

Is there an alternative to keep the code well organized and readable, still preserve the memory?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

如梦初醒的夏天 2024-07-12 09:15:54

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

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

将声明限定为“静态”将它们存储在主内存中而不是堆栈上。 它们的行为基本上类似于全局变量,但仍然允许您避免过度使用全局变量带来的坏习惯。 您可以很好地将大型、长期存在的缓冲区/变量声明为静态,以减少堆栈的压力。

请注意,如果您的应用程序是多线程的或使用递归,则这不能很好地工作/根本无法工作。

In the event you can spare a lot of main memory but have only a small shred of stack, I suggest evaluating static allocations.

In C, all variables declared inside a function are "automatically managed" which means they're allocated on the stack.

Qualifying the declarations as "static" stores them in main memory instead of on the stack. They basically behave like global variables but still allow you to avoid the bad habits that come with overusing globals. You can make a good case for declaring large, long-lived buffers/variables as static to reduce pressure on the stack.

Beware that this doesn't work well/at all if your application is multithreaded or if you use recursion.

红衣飘飘貌似仙 2024-07-12 09:15:54

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

对于 GCC,尝试添加“-finline-functions”(或 -O3)标志以及可能的“-finline-limit=n”标志。

Turn on optimization, specifically aggressive inlining. The compiler should be able to inline methods to minimize calls. Depending on the compiler and the optimization switches you use, marking some methods as inline may help (or it may be ignored).

With GCC, try adding the "-finline-functions" (or -O3) flag and possibly the " -finline-limit=n" flag.

一腔孤↑勇 2024-07-12 09:15:54

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

正常运行后,读取堆栈空间,看看有多少堆栈空间在运行过程中没有被替换。 设计时至少留出 150%,以便处理所有可能未执行的模糊代码路径。

One trick that I read somewhere inorder to evaluate the stack requirements of the code in an embedded setup is to fill the stack space at the onset with a known pattern(DEAD in hex being my favorite) and let the system run for a while.

After a normal run, read the stack space and see how much of the stack space has not been replaced during the course of operation. Design so as to leave atleast 150% of that so as to tackle all obsure code paths that might not have been exercised.

赤濁 2024-07-12 09:15:54

尝试使调用堆栈更平坦,因此不要将 a() 调用 b(),后者又调用 c(),而 c() 又调用 d (),让 a() 调用 b()c()d() 本身。

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

Try to make the call stack flatter, so instead of a() calling b() which calls c() which calls d(), have a() call b(), c(), and d() itself.

If a function is only referenced once, mark it inline (assuming your compiler supports this).

爱你不解释 2024-07-12 09:15:54

堆栈使用由 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 支持线程本地存储,它在每个线程的基础上分配“全局”存储。 这可能允许您在每个任务的基础上拥有多个独立的全局变量,但这将使您的代码不那么简单。

There are 3 components to your stack usage:

  • Function Call return addresses
  • Function Call parameters
  • automatic(local) variables

The key to minimizing your stack usage is to minimize parameter passing and automatic variables. The space consumption of the actual function call itself is rather minimal.

Parameters

One way to address the parameter issue is to pass a structure (via pointer) instead of a large number of parameters.


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

do this instead:


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

This strategy is good if you pass down a lot of parameters. If the parameters are all different, then it might not work well for you. You would end up with a large structure being passed around that contains many different parameters.

Automatic Variables (locals)

This tend to be the biggest consumer of stack space.

  • Arrays are the killer. Don't define arrays in your local functions!
  • Minimize the number of local variables.
  • Use the smallest type necessary.
  • If re-entrancy is not an issue, you can use module static variables.

Keep in mind that if you're simply moving all your local variables from local scope to module scope, you have NOT saved any space. You traded stack space for data segment space.

Some RTOS support thread local storage, which allocates "global" storage on a per-thread basis. This might allow you to have multiple independent global variables on a per task basis, but this will make your code not as straightforward.

七分※倦醒 2024-07-12 09:15:54

根据您的编译器以及优化选项的积极程度,您进行的每个函数调用都会使用堆栈。 因此,首先您可能需要限制函数调用的深度。
一些编译器确实对简单函数使用跳转而不是分支,这将减少堆栈的使用。 显然,您可以通过使用汇编器宏跳转到函数而不是直接函数调用来完成相同的操作。

正如其他答案中提到的,内联是一种可用的选择,尽管这确实以更大的代码大小为代价。

另一个占用堆栈的区域是局部参数。 您确实可以控制这个区域。 使用(文件级)静态将避免以静态内存分配为代价的堆栈分配。 全局变量同样如此。

在(真正的)极端情况下,您可以为函数制定一个约定,使用固定数量的全局变量作为临时存储来代替堆栈上的局部变量。 棘手的一点是确保使用相同全局变量的函数不会同时被调用。 (因此公约)

Depending on your compiler, and how aggressive your optimisation options are, you will have stack usage for every function call you make. So to start with you will probably need to limit the depth of your function calls.
Some compilers do use jumps rather than branches for simple functions which will reduce stack usage. Obviously you can do the same thing by using, say, an assembler macro to jump to your functions rather than a direct function call.

As mentioned in other answers, inlining is one option available although that does come at the cost of greater code size.

The other area that eats stack is the local parameters. This area you do have some control over. Using (file level) statics will avoid stack allocation at the cost of your static ram allocation. Globals likewise.

In (truly) extreme cases you can come up with a convention for functions that uses a fixed number of global variables as temporary storage in lieu of locals on the stack. The tricky bit is making sure that none of the functions that use the same globals ever get called at the same time. (hence the convention)

回梦 2024-07-12 09:15:54

你能用全局变量替换一些局部变量吗?
数组尤其会占用堆栈。

如果情况允许您在函数之间共享一些全局变量,
您有机会减少内存占用。

权衡成本是增加复杂性,以及与可能更小的内存占用相比,函数之间不必要的副作用的更大风险。

你的函数中有哪些类型的变量?
我们谈论的尺寸和限制是什么?

Can you replace some of your local variables by globals?
Arrays in particular can eat up stack.

If the situation allows you to share some globals between some those between functions,
there is a chance you can reduce your memory foot print.

The trade off cost is increased complexity, and greater risk of unwanted side effects between functions vs a possibly smaller memory foot print.

What sort of variables do you have in your functions?
What sizes and limits are we talking about?

作死小能手 2024-07-12 09:15:54

我认为您可能想象了一个这里不存在的问题。 大多数编译器在堆栈上“分配”自动变量时实际上并不执行任何操作。

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

请注意,堆栈内存已经存在并已分配,不会发生初始化,唯一涉及的处理是传递堆栈指针。

唯一出现问题的情况是所有三个函数都使用大量存储空间,然后堆栈必须容纳所有三个函数的内存。 尝试将分配大量存储空间的函数保留在调用堆栈的底部,即不要从中调用另一个函数。

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

I think you may be imagining a problem which doesnt exist here. Most compilers don't actually do anything when they "allocate" automaticic variables on the stack.

The stack is allocated before "main()" is executed. When you call function b() from function a() the address of the storage area immediately after the last variable used by a is passed to b(). This becomes the start of b()'s stack if b() then calls function c() then c's stack starts after the last automatic variable defined by b().

Note that the stack memory is already there and allocated, that no initialisation takes place and the only processing involved is passing a stack pointer.

The only time this becomes a problem would be where all three functions use large amounts of storage the stack then has to accomadate the memory of all three functions. Try to keep functions which allocate large amounts of storage at the bottom of the call stack i.e. dont call another function from them.

Another trick for memory constained systems is to split of the memory hogging parts of a function into separate self contained functions.

浮云落日 2024-07-12 09:15:54

是的,RTOS 确实会耗尽 RAM 用于任务堆栈的使用。 我的经验是,作为 RTOS 的新用户,倾向于使用不必要的任务。

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

Yes, an RTOS can really eat up RAM for task stack usage. My experience is that as a new user of an RTOS, there's a tendency to use more tasks than necessary.

For an embedded system using an RTOS, RAM can be a precious commodity. To preserve RAM, for simple features it can still be effective to implement several features within one task, running in round-robin fashion, with a cooperative multitasking design. Thus reduce total number of tasks.

风吹短裙飘 2024-07-12 09:15:54

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

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

If you need to start preserving stack space you should either get a better compiler or more memory.

Your software will typically grow (new features,...) , so if you have to start a project by thinking about how to preserve stack space it's doomed from the beginning.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文