学而时习之:C语言中的"悬空指针"、"空类型指针"、"野指针"

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 *,反之亦然(需要强制类型转换)。

关键限制

  1. 不能直接被解引用(编译器不知道数据大小)。
  2. 不能进行指针算术(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 * 就像"万能容器",在需要编写通用接口 (如 mallocqsort线程库)时必不可少,但使用时必须记得:先转型,再解引用

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,杜绝"野"状态。
相关推荐
煤球王子4 小时前
学而时习之:C语言中的内存管理
c
。。。9045 天前
mit6s081 lab8 locks
操作系统·c
CAU界编程小白5 天前
数据结构系列之堆
数据结构·c
煤球王子7 天前
学而时习之:C语言中文件操作Error处理
c
煤球王子7 天前
学而时习之:C语言中的Exception处理
c
BlackQid10 天前
深入理解指针Part3——指针与数组
c
要做朋鱼燕10 天前
【AES加密专题】1.AES的原理详解和加密过程
运维·网络·密码学·c·加密·aes·嵌入式工具
煤球王子11 天前
学而时习之:C语言中的Error处理
c
qq_4378964315 天前
unsigned 是等于 unsigned int
开发语言·c++·算法·c