c内存模型如下:
+--------+ Last Address of RAM
| Stack |
| | |
| v |
+--------+
RAM | |
| |
+--------+
| ^ |
| | |
| Heap |
+--------+
| ZI |
+--------+
| RW |
+========+ First Address of RAM
堆栈和堆空间以相反的方向增加。它们会在中间相互重叠。所以我的问题是:
- 在裸机环境中,malloc何时返回NULL?
- 在裸机环境中,如何防止堆栈与堆重叠?
@WikiWang是正确的,如果您正在使用静态的编译时内存布局(edit尽管您必须以某种方式告诉您的malloc
实现堆的末端在哪里)。
如果不是,假设您指的是裸机,则取决于主板支持包中的C库实现。库必须提供一些brk(2)
的实现或具有类似效果的函数。malloc
工作在brk
或sbrk
设置的内存区域内。例如,查看malloc源调用宏MORECORE
,默认为sbrk
。您的C库将不得不使用内核调用以外的东西:)
堆栈的大小在编译时确定。
+--------+ Last Address of RAM
| Stack |
| | |
| v |
+--------+
RAM | |
+--------+ Stack Limit
| |
+--------+
| ^ |
| | |
| Heap |
+--------+
| ZI |
+--------+
| RW |
+========+ First Address of RAM
malloc返回null如果没有足够的空间给请求的堆。
防止堆栈溢出是你的责任!
malloc何时返回NULL?
这可能取决于C编译器和库的实现。例如,我的malloc
实现调用sbrk
,这是Linux环境下的一个系统调用。由于我们的MCU上没有Linux,我提供了我自己的sbrk
实现,如下所示:
// Global variables.
extern unsigned int _heap;
extern unsigned int _eheap;
static caddr_t heap = NULL;
caddr_t _sbrk(int incr)
{
caddr_t prevHeap;
caddr_t nextHeap;
if (heap == NULL) { // first allocation
heap = (caddr_t) & _heap;
}
prevHeap = heap;
// Always return data aligned on a 8 byte boundary
nextHeap = (caddr_t) (((unsigned int) (heap + incr) + 7) & ~7);
if (nextHeap >= (caddr_t) & _eheap) {
errno = ENOMEM;
return ((void*)-1); // error - no more memory
} else {
heap = nextHeap;
return (caddr_t) prevHeap;
}
}
和_eheap
在链接器脚本中定义:
PROVIDE ( _eheap = ALIGN(ORIGIN(ram) + LENGTH(ram) - 8 ,8) );
有了这个,malloc
在我的程序将返回错误,只有当它到达内存的结束。无法识别堆栈顶部地址,因此我无法通过检查malloc
返回值来诊断可能的堆栈损坏。
如何防止堆栈与堆重叠?
您可以为sbrk
定义自己的返回错误的限制,这将防止malloc
入侵堆栈内存。
1)裸金属,您可能首先不想使用malloc
2)裸金属:你拥有内存并管理它,所以你是唯一可以回答问题的人。
3)即使在非裸机(windows, linux等)上,堆栈撞击堆或运行到代码空间也不是你通常得到保护的事情,你需要告诉编译器,如果它支持的话,添加大量的代码,或者你只是设计你的软件正确的不碰撞。
你的内存分配方案是什么,你的代码做什么,如果你告诉这段代码它可以从什么空间分配。如果编译器完全支持它,您可能需要告诉编译器空间或运行时指示当前堆的顶部在哪里。或者编译器依赖于该空间被模式填充,并且在分配之前检查该模式,这意味着在类似的情况下,您需要用该模式填充该内存。首先,您需要了解编译器的输出是否以及如何防止堆栈与数据或程序空间发生冲突。
通常你可以通过你的系统/软件设计知道你的堆栈在最坏的情况下会有多深,以及你还剩下多少内存。当你在设计和实现时,你要验证最大堆栈深度,你最终需要多少内存,并确保你没有超额订阅系统中处理器可用的内存。
裸机,没有理由期望编译器、库或其他工具为您做这项工作。
malloc
将在分配失败时返回NULL。通常这是由于缺乏记忆。在自由运行的模型中,堆和堆栈没有限制,它总是会给你内存(即使这意味着与堆栈冲突)。
幸运的是,这永远不会发生。通常堆和堆栈有固定的最大大小,所以检查更容易。例如,如果库malloc
调用您的sbrk
(典型情况),您可以编写sbrk
,以便它拒绝扩展超过堆栈限制的堆。这避免了堆与堆栈的碰撞。
另一种方式(堆栈碰撞到堆)更棘手。首先,您无法从堆栈溢出中恢复,因此这里的策略是生成有用的调试信息并停止系统以避免处理损坏的内存。从技术上讲,编译器可以为每个堆栈分配添加检查,但这会明显减慢代码的速度(我甚至不知道是否有编译器支持它——可能有,使用一些工具)。如果您的MCU有内存保护单元(MPU),您可以在堆栈限制之上配置一个小的不可访问区域,以便堆栈溢出将产生MPU故障。这种技术通常被称为保护页(如果您愿意,它很像带有内存断点的保护字节)。然而,要做到这一点,异常需要使用不同的堆栈(否则您将处于同一条船上)。请注意,栈金丝雀不能防止栈堆碰撞。
也就是说,在裸机中,你是设计内存布局的人,你可以根据你的需要设置它。malloc
通常不受欢迎,因为它是不确定的。此外,您应该分析堆栈使用情况,并了解最大堆栈深度,以便正确地设置堆栈大小。