3. 空指针与野指针

任务目标

本案例通过三个最小可运行示例,分别演示空指针的合法判断与非法解引用、野指针的产生原因,以及如何通过“先置空再释放”和“出作用域置空”两种手段避免野指针。最终给出一个可执行的验证路径,帮助你在本地观察两类指针错误的典型表现。

核心代码

#include <stdio.h>
#include <stdlib.h>

/* 示例1:空指针的合法判断与非法解引用 */
void null_pointer_demo(void) {
    int *p = NULL;

    /* 合法用法:解引用前判空 */
    if (p != NULL) {
        *p = 10;
    } else {
        printf("[空指针] p 为 NULL,跳过解引用,安全\n");
    }

    /* 非法用法:直接解引用空指针(运行时会崩溃) */
    /* 取消下面注释可观察段错误 */
    /* *p = 10; */
}

/* 示例2:野指针的产生——指向已释放的内存 */
void wild_pointer_demo(void) {
    int *p = (int *)malloc(sizeof(int));
    *p = 42;
    printf("[野指针] 释放前 *p = %d\n", *p);

    free(p);
    /* 此时 p 是野指针:仍保存已释放内存的地址 */
    /* 取消下面注释可观察到不确定行为(可能打印垃圾值或崩溃) */
    /* printf("释放后 *p = %d\n", *p); */
}

/* 示例3:避免野指针——释放后立即置空 */
void safe_free_demo(void) {
    int *p = (int *)malloc(sizeof(int));
    *p = 7;
    printf("[避免野指针] 释放前 *p = %d\n", *p);

    free(p);
    p = NULL;  /* 关键:释放后立即置空 */

    if (p == NULL) {
        printf("[避免野指针] p 已置空,后续判空可拦截非法访问\n");
    }
}

int main(void) {
    null_pointer_demo();
    wild_pointer_demo();
    safe_free_demo();
    return 0;
}

编译与运行

gcc -o pointer_demo pointer_demo.c -Wall
./pointer_demo

验证方法与判定标准

  • 正常路径:运行程序应看到三行输出,依次为“空指针跳过解引用”“释放前 *p = 42”“避免野指针置空”。程序正常退出,无崩溃。
  • 空指针崩溃验证:取消示例1中/* *p = 10; */的注释,重新编译运行,程序应因段错误(Segmentation Fault)退出——这证明空指针解引用是非法操作。
  • 野指针风险验证:取消示例2中printf("释放后 *p = %d\n", *p);的注释,重新编译运行,程序可能打印垃圾值、崩溃或表现正常——这种不确定行为正是野指针的危害特征。
  • 避免野指针验证:示例3中free(p)后立即执行p = NULL,后续通过if (p == NULL)判空即可安全拦截,不会访问非法内存。

关键要点

  • 空指针(NULL)是“不指向任何对象”的合法状态,解引用前判空是标准防御写法。
  • 野指针是“指向已释放或无效内存”的指针,其值本身无法预判,解引用结果未定义。
  • 避免野指针的两条铁律:free后立即置NULL;指针离开作用域前确保不再被引用。
空指针 vs 野指针:生命周期对比 空指针 (NULL) int *p = NULL; 指向地址0x0 if (p != NULL) { ... } ✓ 安全:判空后访问 ✗ 解引用 → 段错误 野指针 (Wild) int *p = malloc(...); 指向堆内存地址0x1234 free(p) p 仍保存 0x1234 但内存已释放 ✗ 解引用 → 未定义行为 ✓ free后置NULL 核心区别 空指针: 值确定(0x0),可判空 解引用必然崩溃(可预期) 野指针: 值不确定(残留地址) 解引用结果不可预测 可能崩溃/垃圾值/静默错误 避免野指针: free(p); p = NULL;