我应该使堆栈段大还是堆段大?

时间:2012-03-14 19:47:19

标签: c memory-management embedded stack heap

我正在为内存非常有限的微处理器编程设计,我必须在不同的功能中使用“大量”内存。我不能有一个大的堆栈段,堆段,数据段,我必须选择哪个做大,哪个做小。我总共大约32KB,

我对文本段使用大约20KB,其余为12KB。我需要一个4KB的缓冲区来传递给不同的函数(SPI Flash扇区大小)。应该在哪里初始化那个大缓冲区?

所以我的选择是:

1)如果我在函数开头声明缓冲区,那么堆栈需要变大

spiflash_read(...)
{
  u8 buffer[4096]; // allocated on stack 
  syscall_read_spi(buffer,...)
}

2)动态分配,堆需要变大

spiflash_read(...)
{
  u8 *buffer = (u8*) malloc(4096); // allocated in heap
  syscall_read_spi(buffer,...)
}

3)静态分配,巨大的不足,不能在“SPI库”之外使用。

static u8 buffer[4096]; // allocated in data section.

spiflash_read(...)
{
  syscall_read_spi(buffer,...)
}

我的问题是实施此设计的最佳方式是哪种?有人可以解释一下推理吗?

4 个答案:

答案 0 :(得分:9)

静态分配始终是运行时安全的,因为如果内存不足,链接器将在生成时告诉您,而不是代码在运行时崩溃。但是,除非在执行期间永久性地需要内存,否则它可能会浪费,因为分配的内存不能重复用于多种用途,除非您以这种方式显式编码。

动态内存分配是运行时可检查的 - 如果用完了堆,malloc()会返回一个空指针。但是,您需要测试返回值,并根据需要释放内存。动态内存块通常是4或8字节对齐,并且承载堆管理数据开销,这使得它们对于非常小的分配而言效率低。此外,频繁分配和释放大量不同的块大小会导致堆碎片和内存浪费 - 对于“永远在线”的应用程序来说,这可能是灾难性的。如果您从未打算释放内存,并且它将始终被分配,并且您知道 apriori 您需要多少,那么您可能会更好地使用静态分配。如果您有库源,则可以修改malloc以立即暂停内存分配失败,以避免必须检查每个分配。如果分配大小通常具有几个常见大小,则固定块分配器而不是标准malloc()可能更可取。它更具确定性,您可以实施使用情况监控,以帮助优化每个大小的块大小和数量。

堆栈分配是最有效的,因为它会根据需要自动获取并返回内存。但是它也很少或没有运行时检查支持。通常,当发生堆栈溢出时,代码将无法确定地失败 - 并且不一定在根本原因附近。一些链接器可以生成堆栈分析输出,该输出将通过调用树计算最坏情况的堆栈使用情况;你应该使用它,如果你有这个设施,但请记住,如果你有一个多线程系统,将有多个堆栈,你需要检查每个的入口点的最坏情况。此外,lonker不会分析中断堆栈的使用情况,您的系统可能有一个单独的中断堆栈,或者共享系统堆栈。

我要解决这个问题的方法当然不是在堆栈上放置大型数组或对象,而是遵循以下过程:

  1. 使用链接器堆栈分析来计算最坏情况的堆栈使用情况,必要时允许为ISR添加额外的堆栈。分配那么多堆栈。

  2. 静态分配执行期间所需的所有对象。

  3. 使用链接映射来确定剩余的内存量,将几乎所有内存分配给堆(您的链接器或链接器脚本可以自动执行此操作,但如果您必须明确设置堆大小,请保留一些未使用的内容,否则每次添加新的静态对象或扩展堆栈时,都必须调整堆的大小。从堆中分配所有大型临时对象,并保持静默释放分配的内存。
  4. 如果您的库包含堆诊断功能,您可以在代码中使用它们来监视堆使用情况,以检查您与耗尽的距离。

    链接器分析“最坏情况”可能会更大,你在实践中看到的 - 最糟糕的情况是我永远不会被执行的路径。您可以使用特定字节(例如0xEE)或模式预填充堆栈,然后在进行大量测试和操作后,检查“高潮”标记并以此方式优化堆栈。谨慎使用这种技术;您的测试可能无法涵盖所有​​可预见的情况。

答案 1 :(得分:3)

这取决于你是否需要一直缓冲。如果90%的工作都花在了缓冲区上,那么我会把它放在数据段

如果对于给定的函数只是瞬时需要,那么将它放在堆栈上。这样做很便宜,意味着你可以重复使用这个空间。这意味着你必须有一个大堆栈

否则将它放在堆上。

如果您受此内存限制,您应该对内存消耗进行详细分析。一旦你变得那么小,你就不能把它当作'普通'来对待,把它扔在OS /运行时,开发。我见过嵌入式开发商店,不允许进行任何动态内存分配;每件事都是预先计算并静态分配的。虽然它们可能具有多用途存储区(例如,通用IO缓冲区)。回到我的COBOL时代,这是你工作的唯一方式(今天的年轻人......,发牢骚,发牢骚......)

答案 2 :(得分:0)

传统的答案是,您应该对运行时进行装配,以便堆栈和堆相互增长。这允许你忽略哪一个需要“更大”,只是担心如果你没有分配足够的空间TOTAL会发生什么。

答案 3 :(得分:0)

问题是,你真的需要一次读取4096个字节吗?

如果您的数据对象较小,则只能读取必要的大小。

即使您只能擦除4kb页面,也不需要将完整的块缓存在RAM中,因为缓存它,擦除然后重写它是个坏主意。

通常情况下,如果第一页已满,您可以将所需数据以小块的形式复制到新页面,如果第二页已满,则再次删除第一页。

当其中一个操作正在运行时,这也是安全的,同时断电。