Linux下C语言free函数如何检测内存越界?代码现象解析
Linux下malloc/free的越界访问行为与free实现解析
问题现象
在Ubuntu 22.04 x86-64环境下运行两段C代码,出现不同的越界访问结果:
第一段(写越界)
#include <malloc.h> #include <unistd.h> int main() { char* s = malloc(8 * 1024 * sizeof(char)); for (int i = 0; i < 10 * 1024; ++i) { // 越界访问 s[i] = i; // 写入s } usleep(1); // 避免编译器优化掉s free(s); // 触发崩溃 }
程序并未在写越界赋值时崩溃,反而在调用free(s)时触发错误:double free or corruption (!prev)。
第二段(读越界)
#include <malloc.h> #include <stdio.h> #include <unistd.h> int main() { char* s = malloc(8 * 1024 * sizeof(char)); for (int i = 0; i < 10 * 1024; ++i) { printf("%d\n", (int)s[i]); // 读取s } usleep(1); free(s); // 无错误 }
改为读越界操作后,调用free(s)未出现任何错误。
对比Windows平台:写越界操作会直接触发页错误崩溃。
Linux下free函数的实现与内部操作
Ubuntu 22.04使用的是glibc的ptmalloc2内存分配器,free的核心逻辑如下:
- 空指针快速返回:如果传入的指针是
NULL,直接终止操作。 - 定位元数据:ptmalloc2给用户返回的内存指针,实际是内存块的起始地址向后偏移了一段——这段偏移区域存储着该内存块的元数据,包括块大小、分配状态、前后块链表指针等。
free会通过用户指针反向定位到对应的元数据块。 - 完整性校验:检查元数据中的魔术值、块大小对齐标记是否合法,同时验证相邻内存块的状态,确保内存未被非法篡改。
- 合并空闲块:如果当前释放块的前/后相邻块处于空闲状态,会将它们合并成一个大的空闲块,减少内存碎片。
- 内存归还与复用:若合并后的空闲块足够大(超过阈值,如128KB),则通过
munmap系统调用将内存归还给操作系统;否则将空闲块加入对应大小分类的空闲链表,供后续malloc调用复用。
现象原因解释
写越界触发free崩溃的原因
你申请了8KB内存,但写入了10KB数据,越界的2KB直接覆盖了当前内存块的后续元数据(或相邻内存块的元数据)。当free执行时,读取到被篡改的元数据,触发了ptmalloc2的内存完整性保护机制,因此抛出错误。
写越界时未立即崩溃,是因为越界区域仍属于进程的地址空间——ptmalloc2会提前向操作系统申请更大的内存页,在用户空间自行管理,未触及进程地址空间边界,因此不会触发页错误。
读越界无错误的原因
读越界仅读取了不属于当前块的内存数据,未篡改元信息。free执行时校验的元数据保持完整,因此不会触发错误。但这种行为属于未定义行为,可能在其他场景下导致不可预测的问题。
与Windows的差异
Windows默认的内存分配器(如CRT的malloc)会在内存块前后设置保护页或更严格的边界检查,写越界触及保护页时会立即触发页错误崩溃;而glibc的ptmalloc2更侧重内存复用效率,边界检查主要在free阶段执行,因此不会立即崩溃。
内容的提问来源于stack exchange,提问作者Timothy Liu
相关产品推荐
相关产品推荐

