我正在体验嵌入式环境中的堆栈/堆冲突(有关某些背景,请参阅this question)。
我想尝试重写代码,以便它不会在堆上分配内存。
我可以在不使用C的堆中编写应用程序吗?例如,如果我需要动态内存分配,我将如何使用堆栈?
答案 0 :(得分:28)
我曾在嵌入式环境中做过一次,我们正在为生物医学机器编写“超级安全”代码。 Malloc()被明确禁止,部分是因为资源限制以及你可以从动态内存获得的意外行为(寻找malloc(),VxWorks / Tornado和碎片,你会有一个很好的例子)。
无论如何,解决方案是提前计划所需的资源,并在包含在单独模块中的向量中静态分配“动态”资源,具有某种特殊用途的分配器给出和收回指针。如果资源耗尽,这种方法可以完全避免碎片问题并帮助获得更精细的错误信息。
这可能听起来很愚蠢,但在嵌入式系统上,特别是在安全关键系统上,最好是事先很好地了解哪些时间和空间资源,如果只是为了确定尺寸的目的硬件。
答案 1 :(得分:8)
有趣的是,我曾经看过一个完全依赖静态分配内存的数据库应用程序。该应用程序对字段和记录长度有很大的限制。即使是嵌入式文本编辑器(我还在颤抖它)也无法创建超过250行文本的文本。这解决了我此时遇到的一些问题:为什么每个客户只允许40条记录?
在严肃的应用程序中,您无法提前计算正在运行的系统的内存要求。因此,根据需要动态分配内存是个好主意。然而,嵌入式系统中常见的情况是预先分配您确实需要的内存,以防止因内存不足而导致的意外故障。
您可以使用alloca()库调用在堆栈上分配动态内存。但是这个内存对于应用程序的执行上下文来说很紧张,并且将这种类型的内存返回给调用者是一个坏主意,因为它将被后来的子例程调用覆盖。
所以我可以用清晰明确的“依赖于”来回答你的问题......
答案 2 :(得分:5)
您可以使用在堆栈上分配内存的alloca()
函数 - 退出函数时将自动释放此内存。 alloca()
是特定于GNU的,您使用GCC因此必须可用。
请参阅man alloca
。
另一种选择是使用可变长度数组,但您需要使用C99模式。
答案 3 :(得分:3)
可以在main()中从堆栈中分配大量内存,并让您的代码稍后进行子分配。这是一件很愚蠢的事情,因为这意味着你的程序占用了实际上并不需要的内存。
我想没有理由(保存某种愚蠢的编程挑战或学习练习)想要避免堆。如果你“听说”堆分配很慢并且堆栈分配很快,那只是因为堆涉及动态分配。如果你要从堆栈中的保留块动态分配内存,那就会很慢。
堆栈分配简单快捷,因为您只能解除堆栈中“最年轻”的项目。它适用于局部变量。它不适用于动态数据结构。
编辑:看过这个问题的动机......
首先,堆和堆栈必须竞争相同数量的可用空间。一般来说,它们相互成长。这意味着如果您以某种方式将所有堆使用量移入堆栈,那么堆栈大小将超过您可用的RAM量,而不是堆栈冲突。
我认为您只需要观察堆和堆栈的使用情况(您可以获取指向本地变量的指针以了解堆栈当前的位置),如果它太高,请减少它。如果您有许多小的动态分配对象,请记住每个分配都有一些内存开销,因此从池中分配它们可以帮助减少内存需求。如果你在任何地方使用递归,请考虑用基于数组的解决方案替换它。
答案 4 :(得分:2)
如果不使用堆内存,则无法在C中进行动态内存分配。在不使用Heap的情况下编写真实世界的应用程序会非常困难。至少,我想不出办法做到这一点。
顺便说一句,你为什么要避免堆?这有什么不妥?答案 5 :(得分:2)
1:是的,你可以 - 如果你不需要动态内存分配,但它可能会有一个糟糕的性能,取决于你的应用程序。 (即不使用堆不会给你更好的应用程序)
2:不,我认为你不能在堆栈上动态分配内存,因为那部分是由编译器管理的。
答案 6 :(得分:2)
是的,这是可行的。将您的动态需求从内存转移到磁盘(或任何可用的大容量存储器)上 - 并因此受到性能损失。
例如,您需要构建和引用未知大小的二叉树。指定描述树节点的记录布局,其中指向其他节点的指针实际上是树文件中的记录号。编写例程,允许您通过向文件写入附加记录来添加到树中,并通过读取记录来查找树,将其子项作为另一个记录号,读取该记录等等。
此技术动态分配空间,但它是磁盘空间,而不是RAM空间。所涉及的所有例程都可以使用静态分配的空间写入 - 在堆栈上。
答案 7 :(得分:2)
嵌入式应用程序需要小心内存分配,但我不认为使用堆栈或您自己的预分配堆就是答案。如果可能,在初始化时从堆中分配所有必需的内存(通常是缓冲区和大型数据结构)。这需要一种不同于我们现在习惯的程序风格,但这是接近确定性行为的最佳方式。
稍后再分配的大型堆仍然会出现内存不足的情况,唯一要做的就是看门狗启动(或类似操作)。使用堆栈听起来很吸引人但是如果要在堆栈上分配大型缓冲区/数据结构,则必须确保堆栈足够大以处理程序可以执行的所有可能的代码路径。这并不容易,最后类似于子分配堆。
答案 8 :(得分:0)
我最关心的是,废弃堆确实有帮助吗?
由于你希望不使用堆来自堆栈/堆冲突,假设堆栈的开始和堆的开始被正确设置(例如,在相同的设置中,小样本程序没有这样的碰撞问题),那么碰撞意味着硬件没有足够的内存供您的程序使用。
不使用堆,确实可以从堆碎片中节省一些浪费空间;但如果你的程序没有使用堆进行一堆不规则的大尺寸分配,那么浪费可能并不多。我会看到你的碰撞问题更多是一个内存不足的问题,仅仅通过避免堆就无法修复。
我对解决此案的建议:
malloc()
以减少堆碎片);或当然,您可以尝试将所有内容推送到预定义的静态内存空间,但这次很可能会将堆栈覆盖到静态内存中。因此,首先要将算法改进为更少的内存消耗,然后再购买更多的内存。
答案 9 :(得分:0)
我会以不同的方式攻击这个问题 - 如果你认为堆栈和堆是冲突的,那么通过防范来测试它。
例如(假设一个* ix系统)尝试mprotect()
最后一个堆栈页面(假设一个固定大小的堆栈),因此无法访问它。或者 - 如果您的堆栈增长 - 那么mmap
堆栈中间的一个页面和堆。如果你在防守页面上得到一个segv,你知道你已经在堆栈或堆的末尾运行;通过查看seg故障的地址,您可以看到哪个堆栈&堆相撞。
答案 10 :(得分:0)
通常可以在不使用动态内存分配的情况下编写嵌入式应用程序。在许多嵌入式应用程序中,由于堆碎片可能会出现问题,因此不推荐使用动态分配。随着时间的推移,很可能没有适当大小的空闲堆空间区域来允许分配内存,除非有适当的方案来处理此错误,否则应用程序将崩溃。有各种方案可以解决这个问题,一种方法是始终在堆上分配固定大小的对象,以便新的分配始终适合释放的内存区域。另一个检测分配失败并对堆上的所有对象执行碎片整理过程(左侧作为读者的练习!)
您没有说明您正在使用的处理器或工具集,但在许多静态,堆和堆栈中,它们被分配给链接器中单独定义的段。如果是这种情况,则必须是您的堆栈在您为其定义的内存空间之外增长。您需要的解决方案是减少堆和/或静态变量大小(假设这两个是连续的),以便有更多可用于堆栈。尽管这会增加碎片问题的可能性,但可以单方面减少堆。确保没有不必要的静态变量将释放一些空间,代价是如果变量是自动的,可能会增加堆栈使用量。