在裸机环境下,malloc何时返回NULL?

4
以下是 C 语言的内存模型:

在 C 中有一个内存模型:

      +--------+   Last Address of RAM
      | Stack  |
      |   |    |
      |   v    |
      +--------+
RAM   |        |
      |        |
      +--------+
      |   ^    |
      |   |    |
      | Heap   |
      +--------+
      |   ZI   |
      +--------+
      |   RW   |  
      +========+  First Address of RAM

栈和堆空间在相反的方向增加。它们会在中间重叠。

  • 在裸机环境下,malloc 什么时候会返回 NULL?
  • 在裸机环境中,如何防止栈与堆重叠?

2
"none OS" 是什么? - Ed Heal
是的,抱歉造成困惑。 - lrouter
2
在裸机环境中,最好不要使用 malloc。相反,使用内存池来管理固定数量的固定大小的内存块。 - user3386109
2
在裸机环境中,malloc 函数通常不存在。如果它确实存在,则应查阅其所属的文档以了解详情。 - user253751
“malloc函数什么时候返回NULL?”--> 请注意,malloc(0)可能会返回NULL,但并不意味着内存不足。结果因情况而异。 - chux - Reinstate Monica
显示剩余5条评论
5个回答

3

@WikiWang 是正确的,如果您正在使用静态编译时内存布局 (编辑尽管您必须要告诉您的malloc实现堆的结束位置)。

如果不是这样,假设您指的是裸机(bare-metal),那取决于您板支持包中的C库实现。该库必须提供某些实现brk(2)或具有类似效果的函数。 malloc 在由 brk sbrk 设置的内存区域内工作。例如,请参见malloc源代码调用宏MORECORE,默认情况下为sbrk。 您的C库将不得不使用其他东西而不是内核调用 :)。


2

1) 在裸机环境中,你可能不想首先使用malloc。

2) 在裸机环境中,你拥有那块内存并管理它,所以只有你能回答这个问题。

3) 即使在非裸机环境中(如Windows、Linux等),栈碰到堆或者运行到代码空间也不是通常会受到保护的事情,你需要告诉编译器(如果支持)添加更多的代码,或者你需要正确地设计你的软件来避免冲突。

你的内存分配方案是什么?你的代码是做什么的?你是否已经告诉这个代码可以从哪个空间分配?如果编译器支持,你可能需要告诉编译器空间或者运行时指示堆顶目前所在的位置。或者,也许编译器依赖于该空间填充一种模式,并在分配之前检查该模式,这意味着在裸机环境中,你需要用那个模式填充那块内存。首先,你需要找出编译器输出如何防止栈与数据或程序空间冲突。

通常,通过系统/软件设计,你知道最坏情况下栈将深入多少,以及从中剩余了多少内存。在设计和实现过程中,你验证最大栈深度、你需要的内存量,并确保你没有超额分配可用于该处理器在该系统中的RAM。

在裸机环境中,没有理由指望编译器、库或其他工具为你完成这项工作。


谢谢。如何检查最大堆栈深度? - lrouter
有两种方法可以检查你的代码。一种是通过对代码进行分析。另一种是采用类似于Valgrind的方法,将RAM填充为某种模式,运行程序,停止并检查RAM,填充模式在哪里结束,堆栈深度有多深。然后通过分析,你需要确认从堆栈消耗的角度来看,你是否走了最深的路径。如果没有正确地进行分析,仅依靠实验,你可能会错过某些路径,并且偏差可能很小或很大。 - old_timer
有没有工具可以完成这项任务? - lrouter

2

栈的大小在编译时确定。

      +--------+   Last Address of RAM
      | Stack  |
      |   |    |
      |   v    |
      +--------+
RAM   |        |
      +--------+   Stack Limit
      |        |
      +--------+
      |   ^    |
      |   |    |
      | Heap   |
      +--------+
      |   ZI   |
      +--------+
      |   RW   |  
      +========+  First Address of RAM

如果请求的堆空间不足,malloc会返回null。

而防止栈溢出是你的责任!


根据您提供的图表,我在我的映射文件中找到了一个标签“heap_limit”和一个“stack_limit”。 - lrouter

2

malloc 何时返回 NULL?

这可能取决于 C 编译器和库的实现。例如,我的 malloc 实现调用 Linux 环境下的系统调用 sbrk。由于 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侵入堆栈内存。


我已经检查了我的映射文件。是的,有一个堆限制。 - lrouter

1

malloc在分配内存失败时会返回NULL。通常是因为内存不足。在自由运行的模型中,堆和栈没有限制,它总是会给你内存(即使这意味着与栈冲突)。

幸运的是,这种情况从未发生过。通常堆和栈都有固定的最大大小,因此检查更容易。例如,如果库malloc调用您的sbrk(典型情况),您可以编写sbrk以拒绝将堆扩展到超出栈限制的范围。这避免了堆与栈的冲突。

另一种情况(堆栈碰撞)更加棘手。首先,你无法从堆栈溢出中恢复太多东西,因此策略是生成有用的调试信息并停止系统以避免在已损坏的内存上工作。从技术上讲,编译器可以为每个堆栈分配添加检查,但这会明显减慢代码(我甚至不知道是否有任何编译器支持它-可能是的,需要进行一些插装)。如果你的MCU有内存保护单元(MPU),你可以配置一个小的不可访问区域在堆栈限制上方,这样堆栈溢出将生成MPU故障。这种技术通常称为“ guard page”(就像guard bytes与内存断点一样,如果你想的话)。然而,要使其正常工作,异常需要使用不同的堆栈(否则你将处于同一船上)。注意stack canaries不能防止堆栈和堆的冲突。
说到底,在裸机编程中,是设计内存布局的人,根据自己的需求可以任意设置。一般不建议使用malloc,因为它是非确定性的。此外,你应该对栈的使用情况进行分析,并知道你的最大栈深度以便适当地调整栈大小。

C库中的malloc函数是否检查堆栈和堆限制? - lrouter
@D.E.T 这取决于你的 libc 如何实现它。在最常见的情况下,它不会直接执行,但它将调用一个你定义的 sbrk 来分配内存,因此你可以在那里检查它。 - Andrea Biondo
谢谢您的解释。 - lrouter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接