我可以在不使用堆的情况下编写 C 应用程序吗?
我在嵌入式环境中遇到了堆栈/堆冲突(请参阅 这个问题了解一些背景)。
我想尝试重写代码,以便它不会在堆上分配内存。
我可以在不使用 C 语言堆的情况下编写应用程序吗? 例如,只有当我需要动态内存分配时,我该如何使用堆栈?
I'm experiencing what appears to be a stack/heap collision in an embedded environment (see this question for some background).
I'd like to try rewriting the code so that it doesn't allocate memory on the heap.
Can I write an application without using the heap in C? For example, how would I use the stack only if I have a need for dynamic memory allocation?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
我曾经在嵌入式环境中做过一次,当时我们正在为生物医学机器编写“超级安全”代码。
Malloc() 被明确禁止,部分原因是资源限制以及动态内存中可能出现的意外行为(查找 malloc()、VxWorks/Tornado 和碎片,您将得到一个很好的例子)。
无论如何,解决方案是提前规划所需的资源,并在单独模块中包含的向量中静态分配“动态”资源,让某种特殊用途的分配器给出和收回指针。 这种方法完全避免了碎片问题,并有助于在资源耗尽时获得更细粒度的错误信息。
这对于大铁来说可能听起来很愚蠢,但是在嵌入式系统上,特别是在安全关键的系统上,最好事先很好地了解需要哪些时间和空间资源,即使只是为了调整硬件大小。
I did it once in an embedded environment where we were writing "super safe" code for biomedical machines.
Malloc()s were explicitly forbidden, partly for the resources limits and for the unexpected behavior you can get from dynamic memory (look for malloc(), VxWorks/Tornado and fragmentation and you'll have a good example).
Anyway, the solution was to plan in advance the needed resources and statically allocate the "dynamic" ones in a vector contained in a separate module, having some kind of special purpose allocator give and take back pointers. This approach avoided fragmentation issues altogether and helped getting finer grained error info, if a resource was exhausted.
This may sound silly on big iron, but on embedded systems, and particularly on safety critical ones, it's better to have a very good understanding of which -time and space- resources are needed beforehand, if only for the purpose of sizing the hardware.
有趣的是,我曾经见过一个完全依赖静态分配内存的数据库应用程序。 该应用程序对字段和记录长度有严格的限制。 即使是嵌入式文本编辑器(我仍然会颤抖地称呼它)也无法创建超过 250 行文本的文本。 这解决了我当时的一些问题:为什么每个客户端只允许 40 条记录?
在重要的应用程序中,您无法提前计算正在运行的系统的内存需求。 因此,根据需要动态分配内存是一个好主意。 尽管如此,在嵌入式系统中,预分配内存是很常见的情况,您确实需要预先分配内存,以防止由于内存不足而导致意外故障。
您可以使用 alloca() 库调用在堆栈上分配动态内存。 但是这个内存对于应用程序的执行上下文来说是很紧张的,将这种类型的内存返回给调用者是一个坏主意,因为它会被以后的子例程调用覆盖。
所以我可能会用清晰明确的“视情况而定”来回答你的问题......
Funnily enough, I once saw a database application which completly relied on static allocated memory. This application had a strong restriction on field and record lengths. Even the embedded text editor (I still shiver calling it that) was unable to create texts with more than 250 lines of text. That solved some question I had at this time: why are only 40 records allowed per client?
In serious applications you can not calculate in advance the memory requirements of your running system. Therefore it is a good idea to allocate memory dynamically as you need it. Nevertheless it is common case in embedded systems to preallocate memory you really need to prevent unexpected failures due to memory shortage.
You might allocate dynamic memory on the stack using the alloca() library calls. But this memory is tight to the execution context of the application and it is a bad idea to return memory of this type the caller, because it will be overwritten by later subroutine calls.
So I might answer your question with a crisp and clear "it depends"...
您可以使用
alloca()
函数在堆栈上分配内存 - 当您退出该函数时,该内存将自动释放。alloca()
是 GNU 特定的,您使用 GCC,因此它必须可用。请参阅
man alloca
。另一种选择是使用变长数组,但需要使用C99模式。
You can use
alloca()
function that allocates memory on the stack - this memory will be freed automatically when you exit the function.alloca()
is GNU-specific, you use GCC so it must be available.See
man alloca
.Another option is to use variable-length arrays, but you need to use C99 mode.
如果不使用堆内存,就无法在 C 中进行动态内存分配。 如果不使用堆,编写现实世界的应用程序将非常困难。 至少,我想不出有什么办法可以做到这一点。
顺便说一句,为什么要避免堆? 这有什么问题吗?
You can't do dynamic memory allocation in C without using heap memory. It would be pretty hard to write a real world application without using Heap. At least, I can't think of a way to do this.
BTW, Why do you want to avoid heap? What's so wrong with it?
1:是的,你可以 - 如果你不需要动态内存分配,但它可能会产生可怕的性能,具体取决于你的应用程序。 (即不使用堆不会给你更好的应用程序)
2:不,我不认为你可以在堆栈上动态分配内存,因为该部分是由编译器管理的。
1: Yes you can - if you don't need dynamic memory allocation, but it could have a horrible performance, depending on your app. (i.e. not using the heap won't give you better apps)
2: No I don't think you can allocate memory dynamically on the stack, since that part is managed by the compiler.
可以在 main() 中从堆栈中分配大量内存,并让您的代码稍后对其进行子分配。 这是一件愚蠢的事情,因为这意味着你的程序占用了它实际上并不需要的内存。
我想不出任何理由(除了某种愚蠢的编程挑战或学习练习)想要避免堆。 如果您“听说”堆分配很慢而堆栈分配很快,那只是因为堆涉及动态分配。 如果要从堆栈中的保留块动态分配内存,速度也会一样慢。
堆栈分配既简单又快速,因为您只能释放堆栈上“最年轻”的项目。 它适用于局部变量。 它不适用于动态数据结构。
编辑:看到这个问题的动机...
首先,堆和堆栈必须竞争相同数量的可用空间。 一般来说,他们会相互成长。 这意味着,如果您以某种方式将所有堆使用量移至堆栈中,那么堆栈大小将恰好超过可用的 RAM 量,而不是堆栈与堆发生冲突。
我认为您只需要观察堆和堆栈的使用情况(您可以获取指向局部变量的指针以了解堆栈目前的位置),如果它太高,请减少它。 如果您有大量动态分配的小型对象,请记住每次分配都会产生一些内存开销,因此从池中对它们进行子分配可以帮助减少内存需求。 如果您在任何地方使用递归,请考虑将其替换为基于数组的解决方案。
It's possible to allocate a large amount of memory from the stack in main() and have your code sub-allocate it later on. It's a silly thing to do since it means your program is taking up memory that it doesn't actually need.
I can think of no reason (save some kind of silly programming challenge or learning exercise) for wanting to avoid the heap. If you've "heard" that heap allocation is slow and stack allocation is fast, it's simply because the heap involves dynamic allocation. If you were to dynamically allocate memory from a reserved block within the stack, it would be just as slow.
Stack allocation is easy and fast because you may only deallocate the "youngest" item on the stack. It works for local variables. It doesn't work for dynamic data structures.
Edit: Having seen the motivation for the question...
Firstly, the heap and the stack have to compete for the same amount of available space. Generally, they grow towards each other. This means that if you move all your heap usage into the stack somehow, then rather than stack colliding with heap, the stack size will just exceed the amount of RAM you have available.
I think you just need to watch your heap and stack usage (you can grab pointers to local variables to get an idea of where the stack is at the moment) and if it's too high, reduce it. If you have lots of small dynamically-allocated objects, remember that each allocation has some memory overhead, so sub-allocating them from a pool can help cut down on memory requirements. If you use recursion anywhere think about replacing it with an array-based solution.
是的,这是可行的。 将您的动态需求从内存转移到磁盘(或任何可用的大容量存储)上,并遭受随之而来的性能损失。
例如,您需要构建并引用未知大小的二叉树。 指定描述树节点的记录布局,其中指向其他节点的指针实际上是树文件中的记录号。 编写例程,让您通过将附加记录写入文件来添加到树中,并通过读取记录、查找其子项作为另一个记录号、读取该记录等来遍历树。
这种技术动态分配空间,但它是磁盘空间,不是 RAM 空间。 所有涉及的例程都可以使用堆栈上的静态分配空间来编写。
Yes, it's doable. Shift your dynamic needs out of memory and onto disk (or whatever mass storage you have available) -- and suffer the consequent performance penalty.
E.g., You need to build and reference a binary tree of unknown size. Specify a record layout describing a node of the tree, where pointers to other nodes are actually record numbers in your tree file. Write routines that let you add to the tree by writing an additional record to file, and walk the tree by reading a record, finding its child as another record number, reading that record, etc.
This technique allocates space dynamically, but it's disk space, not RAM space. All the routines involved can be written using statically allocated space -- on the stack.
嵌入式应用程序需要小心内存分配,但我不认为使用堆栈或您自己的预分配堆是答案。 如果可能,请在初始化时从堆中分配所有所需的内存(通常是缓冲区和大型数据结构)。 这需要与我们大多数人现在习惯的程序风格不同的程序,但这是接近确定性行为的最佳方法。
稍后再分配的大堆仍然会耗尽内存,此时唯一要做的就是启动看门狗(或类似的操作)。 使用堆栈听起来很吸引人,但如果要在堆栈上分配大型缓冲区/数据结构,则必须确保堆栈足够大以处理程序可以执行的所有可能的代码路径。 这并不容易,最终类似于子分配堆。
Embedded applications need to be careful with memory allocations but I don't think using the stack or your own pre-allocated heap is the answer. If possible, allocate all required memory (usually buffers and large data structures) at initialization time from a heap. This requires a different style of program than most of us are used to now but it's the best way to get close to deterministic behavior.
A large heap that is sub-allocated later would still be subject to running out of memory and the only thing to do then is have a watchdog kick in (or similar action). Using the stack sounds appealing but if you're going to allocate large buffers/data structures on the stack you have to be sure that the stack is large enough to handle all possible code paths that your program could execute. This is not easy and in the end is similar to a sub-allocated heap.
我最关心的是,废除堆真的有帮助吗?
由于您不使用堆的愿望源于堆栈/堆冲突,假设堆栈的起始位置和堆的起始位置设置正确(例如,在相同的设置下,小示例程序没有此类冲突问题),那么冲突意味着硬件已经您的程序没有足够的内存。
不使用堆,确实可以节省一些堆碎片造成的浪费空间; 但如果你的程序不使用堆进行一堆不规则的大尺寸分配,那么浪费可能并不多。 我会看到你的碰撞问题更多的是内存不足的问题,仅仅通过避免堆是无法修复的。
我对处理这种情况的建议:
malloc()
以减少堆碎片); 或者当然,您可以尝试将所有内容推入预定义的静态内存空间,但这次很可能会被堆栈覆盖到静态内存中。 因此,首先改进算法以减少内存消耗,然后购买更多内存。
My foremost concern is, does abolishing the heap really helps?
Since your wish of not using heap stems from stack/heap collision, assuming the start of stack and start of heap are set properly (e.g. in the same setting, small sample programs have no such collision problem), then the collision means the hardware has not enough memory for your program.
Not using heap, one may indeed save some waste space from heap fragmentation; but if your program does not use the heap for a bunch of irregular large size allocation, the waste there are probably not much. I will see your collision problem more of an out of memory problem, something not fixable by merely avoiding heap.
My advices on tackling this case:
malloc()
to reduce heap fragmentation); orOf course you may try pushing everything into pre-defined static memory space, but it is very probable that it will be stack overwriting into static memory this time. So improve the algorithm to be less memory-consuming first and buy more memory the second.
我会以不同的方式解决这个问题 - 如果您认为堆栈和堆发生冲突,那么通过防范来测试它。
例如(假设 *ix 系统)尝试使用 mprotect() 处理最后一个堆栈页(假设固定大小的堆栈),使其不可访问。 或者 - 如果您的堆栈增长 - 然后
mmap
堆栈和堆中间的页面。 如果你在你的保护页面上看到一个segv,你就知道你已经跑出了栈或堆的末尾; 通过查看段错误的地址,您可以看到堆栈和内存中的哪一个发生了错误。 堆碰撞了。I'd attack this problem in a different way - if you think the the stack and heap are colliding, then test this by guarding against it.
For example (assuming a *ix system) try
mprotect()
ing the last stack page (assuming a fixed size stack) so it is not accessible. Or - if your stack grows - thenmmap
a page in the middle of the stack and heap. If you get a segv on your guard page you know you've run off the end of the stack or heap; and by looking at the address of the seg fault you can see which of the stack & heap collided.通常可以在不使用动态内存分配的情况下编写嵌入式应用程序。 在许多嵌入式应用程序中,不推荐使用动态分配,因为堆碎片可能会出现问题。 随着时间的推移,很可能没有适当大小的可用堆空间区域来允许分配内存,除非有适当的方案来处理此错误,否则应用程序将崩溃。 有多种方案可以解决这个问题,其中一种方案是始终在堆上分配固定大小的对象,以便新的分配始终适合已释放的内存区域。 另一个用于检测分配失败并对堆上的所有对象执行碎片整理过程(留给读者作为练习!)
您没有说明您正在使用什么处理器或工具集,但在许多静态,堆和堆栈中被分配到链接器中单独定义的段。 如果是这种情况,那么堆栈的增长一定超出了您为其定义的内存空间。 您需要的解决方案是减少堆和/或静态变量大小(假设这两个是连续的),以便堆栈有更多可用空间。 可以单方面减少堆,尽管这会增加出现碎片问题的可能性。 确保没有不必要的静态变量将释放一些空间,但如果变量设为自动,则可能会增加堆栈使用量。
It is often possible to write your embedded application without using dynamic memory allocation. In many embedded applications the use of dynamic allocation is deprecated because of the problems that can arise due to heap fragmentation. Over time it becomes highly likely that there will not be a suitably sized region of free heap space to allow the memory to be allocated and unless there is a scheme in place to handle this error the application will crash. There are various schemes to get around this, one being to always allocate fixed size objects on the heap so that a new allocation will always fit into a freed memory area. Another to detect the allocation failure and to perform a defragmentation process on all of the objects on the heap (left as an exercise for the reader!)
You do not say what processor or toolset you are using but in many the static, heap and stack are allocated to separate defined segments in the linker. If this is the case then it must be that your stack is growing outside the memory space that you have defined for it. The solution that you require is to reduce the heap and/or static variable size (assuming that these two are contiguous) so that there is more available for the stack. It may be possible to reduce the heap unilaterally although this can increase the probability of fragmentation problems. Ensuring that there are no unnecessary static variables will free some space at the cost of possibly increasing the stack usage if the variable is made auto.