C语言 free 后不置 NULL 会发生什么?一文彻底搞懂悬空指针的坑
1. 悬空指针是什么?为什么危险?
在 C 语言中,free()
释放堆内存后,指针本身的值不会改变,仍然指向原来的地址。如果此时再次访问该指针,就是所谓的悬空指针(Dangling Pointer)。
悬空指针问题属于未定义行为(UB),意味着程序可能:
- 运行正常(最“幸运”的情况)。
- 崩溃(常见)。
- 出现逻辑错误(最难排查)。
2. 一个典型的错误示例
下面这段代码在很多项目里出现过:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *buffer = malloc(100);
strcpy(buffer, "Hello, World!");
printf("Before free: %s\n", buffer);
free(buffer); // 释放内存,但未置 NULL
// 程序员以为 buffer 还能用
if (strlen(buffer) == 0) { // ⚠️ 悬空指针
printf("Empty string!\n");
} else {
printf("Buffer: %s\n", buffer);
}
return 0;
}
这段代码的问题:
free(buffer)
后,buffer
仍然保存原来的地址。- 下一次
strlen(buffer)
会访问已经释放的内存,导致未定义行为。
运行结果可能有三种:
- 看似正常:输出原来的字符串。
- 输出乱码:因为这段内存被其他对象覆盖。
- 直接崩溃:出现
Segmentation fault
。
3. 为什么有时候不崩?虚拟机 VS 物理机的差异
很多人会奇怪:
在虚拟机上运行没事,换到物理机或者生产环境就崩了,为什么?
(1) 虚拟机“运气好”
- 内存分配器(glibc malloc)会把
free
的块放到空闲链表。 - 如果程序后续没有新的
malloc
,这块内存可能一直闲置,数据保持原样 → 程序表面上正常运行。
(2) 物理机更容易出问题
- 高并发、多线程、频繁分配/释放 → 这块内存会被很快重用。
- 你再
strlen(buffer)
时,读到的是随机数据 → JSON 解析失败、崩溃。
(3) 调试模式 vs 生产模式
- 有些调试模式(如
glibc
的MALLOC_PERTURB_
)会在free
后填充特定字节(如0xAA
)。 - 生产环境通常关闭这些安全特性 → 风险更大。
4. 如何彻底避免悬空指针?
方法一:free 后立即置 NULL
free(buffer);
buffer = NULL;
if (buffer == NULL) {
printf("Buffer is NULL now!\n");
}
方法二:封装成安全宏
#define SAFE_FREE(p) do { free(p); (p)=NULL; } while(0)
SAFE_FREE(buffer);
方法三:避免用指针状态判断逻辑
不要再写:
if (strlen(buffer) == 0) { ... } // ⚠️危险
改成:
if (buffer == NULL) { ... }
5. 总结
free()
后不置NULL
,指针仍然有效,但所指向的内存已经被释放,访问它就是未定义行为。- 虚拟机上“没事”只是因为内存复用慢,物理机或生产环境必定会暴露问题。
- 最佳实践:
free
后立即置NULL
。- 封装安全宏。
- 避免依赖悬空指针做逻辑判断。
一句话记住
free 不等于清空,free 后不用 NULL,迟早出大事!
THE END