C 语言中的悬空指针、空类型指针、空指针与野指针
在 C 语言里,指针用来保存 变量 或 内存位置的地址 ,以便直接操作内存。然而,某些与指针相关的特殊状态会给程序的内存安全和行为带来隐患,主要包括:
- 悬空指针 (Dangling):指向已被释放的内存
- 空类型指针 (Void):指向一块尚未指定具体类型的数据
- 空指针 (Null):不指向任何有效地址
- 野指针 (Wild):未初始化的指针
1.悬空指针(Dangling Pointer)
当指针所指向的内存已被 free
或超出作用域而被回收,但指针变量本身仍然保留旧地址,就形成悬空指针。继续使用它会导致未定义行为,是 C 程序中常见的 bug 来源。
markdown
**悬空指针** 是指向已经被释放或无效的内存地址的指针。
通俗地说,就是这块内存你已经没有使用权了(比如被`free`了),
但你的指针变量还记录着这个旧地址。
访问或操作悬空指针会导致**未定义行为**,这意味着程序可能崩溃、产生错误数据,或者表现得时好时坏,是C语言中最常见且危险的错误之一。
造成悬空指针的典型场景有三种。
第一种场景: 内存被释放
当指针所指向的内存被显式释放(free
)后,该指针就变成悬空指针。
示例:下面这段代码展示了释放内存后 ptr
成为悬空指针的情形:
c
// C 程序:释放 ptr 指向的内存后,ptr 变为悬空指针
#include <stdio.h>
#include <stdlib.h>
int main(){
int* ptr = (int*)malloc(sizeof(int));
// 执行 free 后,ptr 成为悬空指针
free(ptr);
printf("内存已释放\n");
ptr = NULL; // 解除悬空状态:把指针置为 NULL
return 0;
}
第二种场景: 函数返回局部变量地址
当函数返回一个指向非静态局部变量的指针时,该变量在函数结束后就被销毁,指针随之变成悬空指针。
局部变量在栈上分配,当函数执行结束时,其栈帧被销毁,所有局部变量的内存都会
被回收。如果返回一个指向局部变量的指针,那么调用方得到的就是一个悬空指针。
示例 1:错误用法(悬空指针)
c
#include <stdio.h>
int* fun()
{
int x = 5; // 普通局部变量,函数结束后即被销毁
return &x; // 返回已失效的地址
}
int main()
{
int* p = fun(); // p 成为悬空指针
printf("%d", *p); // 未定义行为,可能输出 0 或 随机值
return 0;
}
输出(示例):
0
示例 2:正确做法(加 static
)
c
#include <stdio.h>
int* fun()
{
static int x = 5; // 静态变量,生命周期贯穿整个程序
return &x; // 地址始终有效
}
int main()
{
int* p = fun();
printf("%d", *p); // 安全访问,输出 5
return 0;
}
输出:
5
结论:把局部变量声明为 static
,其内存不会在函数结束后被回收,因此返回的指针不会悬空。(我觉得这样虽然可以,但是作为程序员尽量也不要返回函数中的局部变量地址)
第三种场景: 变量离开作用域
当变量离开其作用域时,指向该变量的指针就会变成悬空指针。
示例:
c
// C 程序:演示变量离开作用域后产生悬空指针
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr;
// 新建一个代码块
{
int a = 10; // 只在这个块内有效
ptr = &a; // ptr 指向局部变量 a
} // 块结束,a 被销毁
// 此时 ptr 成为悬空指针
printf("%d", *ptr); // 未定义行为,输出垃圾值
return 0;
}
输出(示例):
2355224
2.C 语言中的空类型指针(void pointer)
void *
是一种特殊指针类型,它指向内存中的某个数据,但不携带任何具体类型信息 。换句话说,它"通用"------可以存放任意类型 的地址;任何指针(int *
、float *
、struct *
...)都能直接赋给 void *
,反之亦然(需要强制类型转换)。
关键限制
- 不能直接被解引用(编译器不知道数据大小)。
- 不能进行指针算术(
ptr++
、ptr + 1
都是非法的)。
必须先强制类型转换成具体指针类型,再使用。
语法
c
void *ptrName;
示例程序
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x = 4;
float y = 5.5f;
void *ptr; // 通用指针
ptr = &x; // 指向 int
printf("整型变量 = %d\n", *( (int *)ptr )); // 强制转换后解引用
ptr = &y; // 指向 float
printf("浮点变量 = %f\n", *( (float *)ptr )); // 强制转换后解引用
return 0;
}
运行结果
整型变量 = 4
浮点变量 = 5.500000
总结
void *
就像"万能容器",在需要编写通用接口 (如 malloc
、qsort
、线程库
)时必不可少,但使用时必须记得:先转型,再解引用。
3.C 语言中的空指针(NULL Pointer)
空指针是一个"不指向任何东西"的指针------它的值明确表示"当前没有合法对象或内存地址可指"。当你暂时不知道要让指针指向哪里时,就把它设成 NULL
,以表明"此处为空"。
语法
c
数据类型 *指针名 = NULL;
示例程序
c
#include <stdio.h>
int main()
{
int *ptr = NULL; // 空指针
printf("ptr 的值是 %p\n", ptr);
return 0;
}
运行结果
go
ptr 的值是 (nil)
关键区别
- 未初始化指针:存放的是随机垃圾地址,完全不可预期。
- 空指针 :存放的是由实现定义的固定值 (通常是
0
),明确表示"无有效地址"。
下面用两行代码就能一眼看出"未初始化指针"和"空指针"的区别:
c
#include <stdio.h>
int main(void)
{
int *p1; /* 1. 未初始化指针 ------ 里面是垃圾值 */
int *p2 = NULL; /* 2. 空指针 ------ 固定为 0 或 (nil) */
printf("p1 (未初始化) = %p ← 每次运行都可能不同\n", p1);
printf("p2 (NULL) = %p ← 永远是 (nil)\n", p2);
return 0;
}
一次典型运行结果(不同机器/次数值会变化):
scss
p1 (未初始化) = 0x7ffe4b2a3f40 ← 随机垃圾地址
p2 (NULL) = (nil) ← 固定表示"空"
结论
p1
的值是"随机的",解引用它直接踩雷(未定义行为)。p2
的值是"固定的",一眼就能判断它当前不指向任何有效对象。
NULL vs void *
NULL
是一个特殊值 ,用来给任何类型的指针赋"空"。void *
是一种类型,用来表示"通用指针",需要先强制转型才能使用。
4.C 语言中的野指针(Wild Pointer)
野指针 就是完全没被初始化 的指针------它既没有被赋给合法地址,也没有设为 NULL
,里面保存的是随机的垃圾值。这个值可能指向任意地方,甚至根本不是一个有效地址。
语法(大家千万不要这么做,这么做是错误的哈!纯纯就一大Bug)
c
数据类型 *指针名; /* 只声明,不初始化 → 野指针 */
示例程序
c
#include <stdio.h>
int main()
{
int *p; /* 野指针,内容随机 */
int x = 10;
/* 试图使用野指针 → 未定义行为 */
printf("p 指向的值是: %d\n", *p);
return 0;
}
运行结果
bash
Segmentation Fault (SIGSEGV)
timeout: the monitored command dumped core
/bin/bash: line 1: 32 Segmentation fault
结论
- 野指针的值不可预期,解引用它几乎必定导致段错误或其他未定义行为。
- 养成习惯:声明指针立刻初始化 ------要么指向合法对象,要么设为
NULL
,杜绝"野"状态。