我应该使堆栈段大或堆段大吗?

我正在为内存非常有限的微处理器编程设计,我必须在不同的function中使用“大量”内存。 我不能有一个大的堆栈段,堆段,数据段,我必须选择哪个做大,哪个做小。 我总共大约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,...) } 

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

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

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

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

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

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

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

  3. 使用链接映射来确定剩余多少内存,将几乎所有内存分配给堆(您的链接器或链接器脚本可以自动执行此操作,但如果您必须明确设置堆大小,请保留一些未使用的,否则每次都添加一个新的静态对象,或者扩展堆栈的大小。 从堆中分配所有大型临时对象,并保持静默以释放分配的内存。

如果您的库包含堆诊断function,您可以在代码中使用它们来监视堆使用情况,以检查您与耗尽的距离。

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

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

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

否则把它放在堆上。

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

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

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

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

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

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

当其中一个动作正在运行时,这也是安全的。