malloc在什么情况下会返回NULL?

41

这种情况从未发生在我身上,因为我已经编程多年了。

有人能给我举一个非平凡的程序例子吗,在这个程序中malloc实际上不起作用?

我不是在谈论内存耗尽的情况: 我正在寻找一个简单的情况,当您分配一个由用户给定的边界大小的单个内存块(比如一个整数)时,malloc会失败。


2
在此赢取2分钟内回答最多问题的奖品 :) - Michael Dorgan
@MichaelDorgan - 对你来说可能很容易,但是我所有存在malloc问题的非平凡应用程序都已经交付给客户了,所以我不能自由地发布它们 :) - Martin James
如果您分配一个有限大小的单个块,并且您的界限足够小,使系统始终可以提供内存,那么它当然会始终成功。不过,在这种情况下,您的单次分配玩具程序是微不足道的。 - Useless
1
Lol - 我在手持平台上工作了这么多年,所以从malloc返回NULL对我来说非常容易实现。 - Michael Dorgan
3
如果你的内存泄漏问题随着时间不断加剧,或者出现了严重碎片化现象,那么任何大小的malloc都无法完全保证安全。@Useless - Michael Dorgan
真实的情况是,如果malloc无法成功,它将失败。它能否成功取决于您的系统和堆的当前状态。如果您在主函数中分配一个1字节的块并立即返回,则不太可能失败。如果您有一个真正的程序需要完成一些实际工作,那么在某些情况下可能会失败。这样说是否够清楚明了? - Useless
10个回答

35
你需要在嵌入式系统中做一些工作,那里经常会返回NULL :-)
在现代的大地址空间和后备存储系统中,很难耗尽内存,但在处理大量数据的应用程序(如GIS或内存数据库)或者由于有bug的代码导致内存泄漏的地方,仍然是可能的。
但无论你以前是否经历过这种情况都不重要 - 标准规定它可能发生,所以你应该为此做好准备。我在过去几十年里也没有被车撞过,但这并不意味着我会在过马路时不先看一眼。
关于你的编辑:
引用: 我不是在谈论内存耗尽...
内存耗尽的定义就是malloc不能给你所需的空间。无论是因为分配了所有可用内存,还是堆碎片化导致无法获得连续的块,即使内存区域中所有空闲块的总和更高,或者人为限制了地址空间的使用,都是无关紧要的。
void *malloc (size_t sz) { return NULL; }

C标准不区分故障的模式,只表示成功或失败。

海报问是否有一种情况,malloc()返回0且内存未耗尽。碎片化的情况更加有趣,但需要非平凡代码(针对特定分配器进行调整)才能显示模式。 - Brian Bulkowski
3
布莱恩,我认为我的回答已经涵盖了这一点。内存耗尽是指malloc无法提供您所需的内存。无论您是否完全没有内存或者您请求了60个字节,而分配器有10亿个30字节的块无法合并,都没有关系。在这两种情况下,都属于内存耗尽。无论如何,一个每十次调用就返回NULL的malloc(由真正的虐待狂编写)仍然符合标准。 - paxdiablo

28

可以。

只需尝试使用malloc申请比系统可提供的内存更多的内存(通过耗尽地址空间或虚拟内存中较小的内存,两者之一)即可。

malloc(SIZE_MAX)

可能会这样做。如果不行,重复几次直到用尽为止。


11
我的第一台电脑是一台8位系统,只有56KB的RAM。malloc()经常会返回NULL - Ferruccio
3
谢谢,但那很琐碎。我说的是当你只分配了一个有限的内存块时,它是否仍然可能失败?请看我的编辑。 - RanZilber
5
抱歉,显然我应该发布一个分配数亿个合理大小对象的非平凡程序,并有充分的理由,这将显然产生完全相同的结果。不过,这个程序很可能既具有可读性、简明性又不平凡! - Useless
C-64的基本内存映射到malloc函数? - Michael Dorgan
2
这个答案是内存耗尽的情况。发布者要求一个malloc()返回0,但内存并没有耗尽的情况。 - Brian Bulkowski
1
这是在回答写完后编辑到问题中的。然而,您的虚拟机用完了页面、达到了超额承诺限制或者您的进程用完了连续地址,这些都是不同形式的耗尽:我不知道“内存耗尽”应该指什么。 - Useless

13
任何使用C语言编写的程序,需要动态分配的内存超过操作系统当前允许的限制。
如果您正在使用Ubuntu系统,可以尝试输入以下命令:
 ulimit -v 5000

你运行的任何程序都很可能会崩溃(由于malloc失败),因为你将任何一个进程可用内存的限制降到了一个微不足道的程度。


11

除非内存已被完全保留(或严重碎片化),否则malloc()只有请求大小为零的空间时才会返回一个NULL指针:

char *foo = malloc(0);
根据C99标准第7.20.3节的第1小节规定:

如果请求的内存大小为零,则行为是实现定义的:要么返回空指针,要么行为就像请求了一些非零大小的空间一样,但返回的指针不能用于访问对象。

换句话说,malloc(0) 可能会返回一个NULL指针或一个指向零字节分配的有效指针。


5
选择任何平台,嵌入式平台可能更容易。使用malloc(或new)分配大量内存(或随着时间的推移泄漏内存,甚至通过使用天真的算法使内存碎片化)。完成了。malloc在发生“坏”操作时偶尔会返回NULL
对于您的编辑回复是肯定的。随着时间的推移,内存碎片化可能导致甚至单个int分配失败。还要记住,malloc不仅为int分配4个字节,而且可以获取尽可能多的空间。它有自己的记账工具,通常会最少占用32-64个字节。

1
我喜欢这个答案,因为它谈到了内存碎片和簿记。仅仅因为[物理]内存“存在”并不意味着它总是“可用”的。也就是说,即使分配的数据比总内存少,malloc也可能会失败。(我也喜欢关于虚拟内存和过度承诺的补充回答。) - user166390
你确定吗?请看:http://stackoverflow.com/questions/29613162/why-i-never-see-the-hello-text-in-console-in-this-program?noredirect=1#comment47371957_29613162 - Koray Tugay

5
在一个更或多或少标准的系统上,使用一个单参数的malloc函数,有三种可能的失败模式(据我所知):
1. 所请求的分配大小不被允许。例如,一些系统即使有更多的存储空间可用,也可能不允许分配大于16M的内存。
2. 无法在堆中定位一个默认边界下请求大小的连续自由区域。可能还有很多堆可用,但只是没有足够大的连续自由区域。
3. 分配的总堆已超过某个“人为”的限制。例如,即使有200M的空闲内存可供“系统”在一个组合的堆中使用,用户可能被禁止分配超过100M的内存。
(当然,您可以获得2和3的组合,因为某些系统将非连续的地址空间块分配给堆随着堆的增长,将“堆大小限制”放在块的总和上。)
请注意,有些环境支持额外的malloc参数,如对齐和池ID,这些参数可能会增加它们自己的复杂性。

4

只需查看malloc的手册页。

成功时,函数返回指向所分配内存块的指针。
这个指针的类型总是void*,可以强制转换为所需的数据指针类型以便进行引用。
如果函数未能分配所请求的内存块,则返回空指针。


4
这段话描述了合同条款,但是“什么时候会/会发生”呢?(“……从未发生过……在一个非平凡的程序中,malloc实际上不起作用?……当你只分配一个由用户给定的有限大小的内存块时……malloc仍然可能失败吗?”) - user166390
这甚至不是一个答案。 - Koray Tugay

4

是的。当内核/系统库确定无法分配内存时,Malloc将返回NULL。

通常在现代计算机上看不到这种情况的原因是Malloc并不真正分配内存,而是请求为程序保留一些“虚拟地址空间”,以便您可以在其中写入数据。像现代Linux这样的内核实际上会过度承诺,也就是说,只要所有内存都适配于系统的地址空间(通常是64位平台上的48位),它们就可以让您分配比系统实际可提供的更多的内存(交换+RAM)。因此,在这些系统上,您可能会在触发返回NULL指针之前触发OOM killer。例如,在32位机器上具有512MB RAM的情况下,很容易编写C程序,该程序由于尝试malloc所有可用的RAM +交换而被OOM killer吃掉。

(在Linux上可以在编译时禁用Overcomitting,因此这取决于构建选项是否给定的Linux内核会过度承诺。但是,桌面发行版内核默认启用它。)


2
它在运行时禁用而非编译时,大多数有能力的管理员会将其禁用,除非他们有特定的理由不禁用。 - R.. GitHub STOP HELPING ICE

3

既然您要求提供示例,这里有一个程序,最终会看到 malloc 返回 NULL

perror();void*malloc();main(){for(;;)if(!malloc(999)){perror(0);return 0;}}

什么?你不喜欢故意混淆的代码吗?;)(如果它在你的机器上运行几分钟而不崩溃,请杀掉它,将999更改为更大的数字,然后再尝试。)

编辑:如果无论数字有多大都无法正常工作,则说明您的系统正在说“这里有一些内存!”但只要您不尝试使用它,它就不会被分配。在这种情况下:

perror();char*p;void*malloc();main(){for(;;){p=malloc(999);if(p)*p=0;else{perror(0);return 0;}}

这应该能解决问题。如果我们可以使用GCC扩展,通过将char*p;void*malloc();更改为void*p,*malloc();,我们甚至可以让它更小,但如果您真的想要高尔夫球,您可能会去Code Golf SE。


虽然数据没有被“污染”,也许它并没有真正地在[物理]内存中实现?并不是说有无限的资源... - user166390
1
数据将会立即变得不干净,因为999小于一页。因此,在分配之间的簿记结构中,每页将至少被访问一次。 - R.. GitHub STOP HELPING ICE
1
@Chris:如果分配的空间大于一页,并且在应用程序可用区域中包含一个或多个完整页面,则没有理由指望这些页面已被访问。但是,如果您只保持分配999字节,则连续分配的簿记信息将被分隔不到一页,因此您最终会触及每个被分配的页面。 - R.. GitHub STOP HELPING ICE
这对我来说看起来像是内存耗尽了,发帖者不是要求一个不是内存耗尽的案例吗? - Brian Bulkowski
2
在我的电脑上(macOS Sierra),malloc 从不返回 NULL。相反,在分配了大约50 GiB的虚拟内存后,程序会收到 SIGKILL 信号。 - tbodt
显示剩余2条评论

0

当malloc参数为负数或0,或者堆上没有剩余内存时,会出现问题。我不得不纠正某人的代码,它看起来像这样。

const int8_t bufferSize = 128;
void *buffer = malloc(bufferSize);

这里buffer是NULL的原因是因为bufferSize实际上是-128


2
不完全正确。存储在“bufferSize”中的-128被转换为“size_t”,导致一个_大_数字。由于那么多内存不可用,malloc()返回了NULL。另一方面,它_可能_已经起作用了。当“malloc参数为...0”时是另一回事。 - chux - Reinstate Monica
的确,那是我的错,但这仍然是一个错误。我认为在正常情况下,针对这种特定情况,它将始终返回0。 - Soucup Bogdan
虽然这是一个错误,但是分配的大小为SIZE_MAX-128+1(非常大),可能会返回非NULL示例 - chux - Reinstate Monica

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