在内存中,栈底和堆底的地址应该一样吗?

In memory, should stack bottom and heap bottom have the same address?

我正在使用带有 this 链接器脚本的 tm4c123gh6pm MCU。走到底部,我看到:

...
...
.bss (NOLOAD):
{
    _bss = .;
    *(.bss*)
    *(COMMON)
    _ebss = .;
} > SRAM

_heap_bottom = ALIGN(8);
_heap_top = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size;
_stack_bottom = ALIGN(8);
_stack_top = ORIGIN(SRAM) + LENGTH(SRAM);

看来堆底和栈底是一样的。我已经仔细检查过了:

> arm-none-eabi-objdump -t mcu.axf | grep -E "(heap|stack)"
20008000 g       .bss   00000000 _stack_top
20007000 g       .bss   00000000 _heap_top
00001000 g       *ABS*  00000000 _stack_size
20000558 g       .bss   00000000 _heap_bottom
20000558 g       .bss   00000000 _stack_bottom

是否正确?据我所知,堆栈可能会覆盖堆,是这种情况吗?

如果我刷新这个 FW 'works'(至少现在是这样),但是如果堆栈足够大并且我使用动态内存,我预计它会失败。我观察到我的代码或启动脚本中没有人使用堆栈和底部符号,所以即使我使用堆栈和堆,一切都可以继续工作。 (除非栈和堆是我看不到的人用的特殊符号,是这样吗?)

我想通过以下方式更改最后一部分:

_heap_bottom = ALIGN(8);
_heap_top = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size;
_stack_bottom = ORIGIN(SRAM) + LENGTH(SRAM) - _stack_size + 4; // or _heap_top + 4
_stack_top = ORIGIN(SRAM) + LENGTH(SRAM);

以上是否正确?

如果您编写自己的链接描述文件,那么堆栈和堆的排列方式由您决定。

一种常见的方法是将堆栈和堆放在同一个块中,堆栈从最高地址向最低地址向下增长,而堆从较低地址向最高地址向上增长。

这种方法的优点是你不需要单独计算你需要多少堆或栈。只要任一时刻使用的堆栈和堆的总和小于可用的总内存,那么一切都可以。

这种方法的缺点是当你分配的内存比你拥有的多时,你的堆栈会溢出到你的堆中,反之亦然,你的程序会以各种方式失败,这很难预测或确定它们何时发生。

您问题中的链接描述文件使用了这种方法,但似乎有一个错误,详情如下。

请注意,在谈论 ARM 上的堆栈时使用名称 topbottom 是非常无用的,因为当您将某些内容压入堆栈时堆栈指针的数值减少。当堆栈为空时,堆栈指针的值最高,当堆栈满时,堆栈指针的值最低。 top是指最高地址还是当前指针所在位置,bottom是指最低地址还是push第一项的地址,不明确

在 CMSIS 示例链接器脚本中,堆的下限和上限称为 __heap_base__heap_limit下限 堆栈的上边界分别称为__stack_limit__initial_sp

在此脚本中,符号具有以下含义:

_heap_bottom是堆的最低地址。

_heap_top 是堆不能增长的上限地址,如果你想为堆栈保留至少 _stack_size 字节。

对于 _stack_bottom,似乎脚本作者可能错误地认为 ALIGN(8) 会对齐最近分配的值,因此他们希望 _stack_bottom 是对齐的版本_heap_top,当 _stack_size 字节被推送到它时,它将成为堆栈指针的值。事实上 ALIGN(8) 对齐 . 的值,正如您观察到的那样,它仍然与 _heap_bottom 具有相同的值。

最后_stack_top是内存中的最高地址,也就是栈为空时栈指针的起始值。

如果堆栈限制的值不正确,几乎肯定不会有任何作用,因为这个符号可能从未在代码中使用过。在这个 ARMv7M 处理器上,push 和 pop 指令以及硬件对堆栈的其他访问都假定堆栈是无限资源。使用所有正常 ABI 的编译器也会生成在堆栈增长之前不进行检查的代码。这样做的原因是它是最常执行的操作之一,因此添加额外的指令会削弱性能。不过,下一代 ARMv8M 确实具有对堆栈限制寄存器的硬件支持。

我给你的建议是删除该行。如果有任何东西在使用它,那么您基本上就失去了共享堆栈和堆的全部好处 space。如果您确实要计算并检查它,那么您的建议是正确的,只是您不需要添加 + 4。这将创建一个 4 字节的间隙,该间隙不能用作堆或堆栈。

顺便说一句,我个人更喜欢将堆栈放在内存的底部,将堆放在顶部,彼此远离。这样,如果它们中的任何一个变得比它们应该的更大,它们就会进入一个未分配的地址 space,可以将其配置为立即导致总线故障,而无需任何软件一直检查这些值。