C NULL 与野指针
更新时间:2026-08-26。本文是
languages/c/主题入门层第 37 篇。NULL 和野指针,是 C 程序崩溃的两大"指针杀手"。区别在于:NULL 是"明确的空",可检测;野指针是"失控的地址",防不胜防。这一篇教你怎么认出它们、躲开它们。
本文要回答的问题
- NULL 是什么?为什么解引用它会段错误?
- 野指针是怎么"变野"的?
- 怎么让代码不产生野指针?
一、NULL:明确的"不指向任何地方"
c
#include <stddef.h> // NULL 的定义(stdio.h 等也带)
int *p = NULL; // 明确:p 不指向任何有效对象NULL 的值就是 0(#define NULL ((void*)0))。操作系统把"地址 0"所在页标记为不可访问,所以解引用 NULL 会触发段错误(Segmentation fault)——这是一种"可控的崩溃"。
c
int *p = NULL;
if (p) { ... } // 判空:p 非 NULL 才进入
if (p != NULL) { ... } // 等价写法
*p = 5; // 危险!p 是 NULL,解引用必崩铁律:解引用前先判空,特别是函数参数、malloc 返回值、查找函数的返回值:
c
char *found = strchr(s, 'x');
if (found != NULL) { // 先判断,再使用
*found = 'y';
}二、野指针:指向"已不存在"或"从未存在"的地方
野指针(wild/dangling pointer)有三种来源:
c
// ① 未初始化:地址是垃圾
int *p;
*p = 5; // 灾难:p 指向哪完全随机
// ② 悬垂:指向已释放/已失效的内存
int *q = malloc(sizeof(int));
free(q);
*q = 5; // 灾难:内存已归还,q 还留着旧地址
// ③ 指向已销毁的局部变量
int *r;
{
int local = 42;
r = &local;
} // local 出块销毁
*r; // 灾难:栈被复用,值不可预测| 类型 | 形成 | 后果 |
|---|---|---|
| 未初始化指针 | 声明不赋值 | 随机崩溃 |
| 悬垂指针 | free 后仍使用 | 数据错乱/崩溃 |
| 指向已销毁局部 | 返回局部地址 | 同上 |
野指针为什么可怕:它不一定立刻崩溃——地址可能"碰巧"有效,改写的是程序正在用的其他数据,bug 在几小时后、几百行之外才暴露。这是 C 排错最痛苦的一类问题。
三、预防策略:生命周期管理
| 策略 | 做法 |
|---|---|
| 声明即初始化 | int *p = NULL; 永远不要裸声明 |
| free 后置 NULL | free(p); p = NULL; 再用就是可控的 NULL 崩溃 |
| 不返回局部变量地址 | 数据要活过函数用 malloc 或调用者缓冲 |
| 判空再解引用 | 使用前 if (p != NULL) |
| 谁分配谁释放 | 明确每块内存的 owner,避免别人乱 free |
核心心态:C 不会帮你追指针,你得自己盯着"每块内存的生死"。
四、用工具抓野指针
| 工具 | 作用 |
|---|---|
-Wall -Wextra | 警告"可能未初始化"的使用 |
-fsanitize=address | 悬垂/越界运行时报告 |
| Valgrind | 内存错误综合检测(慢但全) |
-Wuninitialized | 未初始化变量警告 |
bash
gcc -O0 -g -fsanitize=address -o prog prog.c
./prog
# free 后用 → AddressSanitizer: heap-use-after-free开发期跑一遍 ASan/Valgrind,野指针九成现行。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| NULL 解引用 | 段错误(可控) | 先判空 |
| 未初始化指针 | 随机崩溃 | 声明即初始化 |
| free 后还用 | use-after-free | 置 NULL + ASan |
| 返回局部地址 | 悬垂 | 改设计 |
用 == 比较两个野指针 | 无意义 | 避免产生野指针 |
六、与本站主线衔接
- 段错误的机制与排查(gdb、core dump),见空指针与段错误(下一篇);
- 内存生命周期的完整管理(malloc/free),见内存管理入门;
- ASan/Valgrind 完整指南,见 crash 主题。
一句话总结
NULL 是"明确空",判空可防、崩溃可控;野指针是"失控地址"——未初始化、free 后用、返回局部地址三大来源;对策就是"声明即初始化、free 后置 NULL、使用前判空",再配 ASan 兜底。