malloc/calloc/realloc存在哪些执行失败的边界场景?
malloc/calloc/realloc 的失败场景、边界与实际普遍性
各函数专属的失败边界用例
malloc- 申请大小超过系统给当前进程划定的虚拟内存可寻址上限(比如32位用户态进程最多只能用2~3G用户态地址空间,申请超过这个阈值的内存会直接返回NULL)
- 传入的size参数因为计算整数溢出变成接近
SIZE_MAX的超大值,直接触发分配失败 - 边界特殊值:当传入size为0时,行为是实现定义的——部分实现会返回合法的可写最小内存块,部分实现会返回NULL,这个场景不属于常规内存不足的失败,很容易被误判
calloc- 自带整数溢出校验:当传入的「元素数 × 单元素大小」的计算结果发生溢出时,不会走到实际申请内存的步骤,直接返回NULL(区别于先做乘法再传参给malloc的场景:后者如果乘法溢出得到的size为0,部分实现会返回合法指针,但calloc会直接拦截这类异常场景)
- 因为calloc需要对申请的内存做全量清零,哪怕申请的虚拟地址空间足够,清零过程中如果碰到系统物理内存不足、无法支撑页填充的情况,也会返回分配失败
realloc- 传入的old指针不是之前malloc/calloc/realloc返回的合法堆指针时,部分内存分配实现会直接返回NULL(这类场景本质是代码bug,不是资源不足导致的失败)
- 扩容时如果当前堆块后续没有足够的连续空闲内存,需要寻找新的连续大块内存拷贝旧数据,当找不到符合要求的新内存块时,会直接返回NULL——注意这个场景下原有的旧内存块不会被释放,很多写法直接把返回值覆盖原指针的代码,一旦碰到这种失败就会直接造成内存泄漏
- 同样存在size为0的实现定义行为:部分实现下
realloc(ptr, 0)等价于释放原ptr并返回NULL,不属于常规的内存不足失败
跨函数通用的失败触发场景
- 触达系统层面的内存硬限制:比如单进程的虚拟内存地址空间上限(
RLIMIT_AS)被配置得极低,哪怕宿主机物理内存充足,只要申请的地址空间超过阈值就会分配失败 - 堆元数据被破坏:之前的代码存在堆越界写问题,修改了堆块的管理元数据,后续的内存分配逻辑检测到元数据异常时,会直接返回NULL甚至主动终止进程
- 系统开启严格的内存过度提交限制时,申请内存的大小直接超过当前可分配的物理内存+swap容量,不会走懒分配的流程,直接在申请接口返回失败
实际开发中这类失败的普遍程度
- 对普通运行在通用桌面系统、标准Linux环境下的常规应用(日常申请内存规模在几十到几百M级别):在系统正常负载下,这类失败非常少见——通用系统默认打开内存过度提交机制,只要申请的虚拟地址空间足够,哪怕暂时没有对应的物理内存,分配接口也会直接返回成功,真正的内存不足会发生在后续实际访问内存页、触发缺页异常的时候,由内核的OOM killer直接终止进程,根本走不到分配函数返回NULL的分支。
- 但在特定场景下,这类失败属于高频可预期的情况:
- 嵌入式、RTOS等无虚拟内存、无过度提交机制的运行环境,只要当前堆的剩余空闲块满足不了申请的连续内存大小,立刻就会返回失败
- 容器、沙箱等带cgroup内存隔离的环境,只要进程申请的内存超过了被分配的内存配额,哪怕宿主机还有大量空闲内存,也会直接触发分配失败甚至被终止
- 代码存在整数溢出bug,计算出来的申请内存大小是异常超大值,这种场景下分配失败是必然结果,也是实际生产环境中触发分配函数返回NULL的最常见来源
- 行业内强调要对这三个函数的返回值做校验,本质不是因为这类失败天天发生,而是这类失败一旦没有被捕获,后续会引发野指针操作、内存泄漏、随机崩溃等极难排查的问题——加上校验的成本极低,完全没必要为了省这几行代码承担后续的排障成本。
内容的提问来源于stack exchange,提问作者Nimit Jain
相关产品推荐
相关产品推荐

