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) 会访问已经释放的内存,导致未定义行为。

运行结果可能有三种:

  1. 看似正常:输出原来的字符串。
  2. 输出乱码:因为这段内存被其他对象覆盖。
  3. 直接崩溃:出现 Segmentation fault

3. 为什么有时候不崩?虚拟机 VS 物理机的差异

很多人会奇怪:

在虚拟机上运行没事,换到物理机或者生产环境就崩了,为什么?

(1) 虚拟机“运气好”

  • 内存分配器(glibc malloc)会把 free 的块放到空闲链表
  • 如果程序后续没有新的 malloc,这块内存可能一直闲置,数据保持原样 → 程序表面上正常运行

(2) 物理机更容易出问题

  • 高并发、多线程、频繁分配/释放 → 这块内存会被很快重用
  • 你再 strlen(buffer) 时,读到的是随机数据 → JSON 解析失败、崩溃。

(3) 调试模式 vs 生产模式

  • 有些调试模式(如 glibcMALLOC_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,指针仍然有效,但所指向的内存已经被释放,访问它就是未定义行为
  • 虚拟机上“没事”只是因为内存复用慢,物理机或生产环境必定会暴露问题。
  • 最佳实践:
    1. free 后立即置 NULL
    2. 封装安全宏
    3. 避免依赖悬空指针做逻辑判断

一句话记住

free 不等于清空,free 后不用 NULL,迟早出大事!

THE END