
点击下面查看作者专栏 🔥🔥C语言专栏🔥🔥 🌊🌊编程百度🌊🌊 🌠🌠如何获取自己的代码仓库🌠🌠

索引与导读
- 悬空指针的概念
- 悬空指针常见的场景
-
- 1)释放内存后未置空指针
- 2)返回栈内存的地址
- 3)作用域失效
- [4)realloc 陷阱](#4)realloc 陷阱)
- 5)多个指针指向同一块内存
- C/C++如何避免悬空指针
- 希望读者多多三连
- 给小编一些动力
- 蟹蟹啦!
- 蟹蟹啦!)
悬空指针的概念
悬空指针是C/C++等手动管理内存的编程语言中一个可能会出现危险的概念❗❗❗❗❗❗
其危害跟野指针大同小异
悬空指针 是指向已释放或无效内存地址的指针,使用悬空指针会导致未定义行为、程序崩溃、数据损坏和安全漏洞
悬空指针 未定义行为 程序崩溃 数据损坏 安全漏洞 表现 : 程序意外退出
严重性: 中等 表现 : 结果错误或数据丢失
严重性: 高 表现 : 被攻击者利用
严重性: 严重
悬空指针常见的场景

1)释放内存后未置空指针
当你使用
free (C)或delete (C++)释放了堆内存,但没有把指针设置为NULL就操作了这个指针这个时候你可能就会...
c
/*场景一*/
#include <stdio.h>
#include <stdlib.h>
int main() {
//1.申请内存
int* p = (int*)malloc(sizeof(int));
*p = 42;
//2.释放内存
free(p);// 此时,p 依然存着刚才那块内存的地址,但是p指向的内存已经被释放了
// p 现在就是"悬空指针"
//3.错误使用
*p = 100; // 使用已释放的内存,未定义行为
// 危险!写入已经归还给操作系统的内存
// 这可能导致程序崩溃,也可能篡改了其他数据
}
因为p指针free之后依然存着刚才那块内存的地址,但是p指向的内存已经被释放了
此时在去操作p指针很容易导致程序崩坏
2)返回栈内存的地址
- 函数内部的局部变量存储在栈上
- 函数结束时,栈帧销毁,局部变量的内存自动回收
- 如果返回这个局部变量的地址,外部拿到的就是悬空指针
c
/*场景二*/
int* wrongFunction() {
int x = 100; // x 是局部变量
return &x; // 返回 x 的地址
} // 函数结束,x 的生命周期结束,内存变为无效
int main() {
int* p = wrongFunction();
// p 指向的是之前 x 的位置,但 x 已经不在了
// p 是悬空指针
*p = 200; // 未定义行为,极度危险
return 0;
}
3)作用域失效
当一个变量因为离开了它的作用域而被销毁(生命周期结束),但外部还有一个指针保留着它的地址时,这个指针就变成了悬空指针
c
int main() {
int *ptr; // 指针定义在外部,生命周期长
{ // 内部代码块开始
int inner_var = 100; // 局部变量,生命周期短
ptr = &inner_var; // 指针指向这个短期变量
printf("Inside: %d\n", *ptr); // 这里是安全的
} // inner_var 在这里"死亡",内存被标记为可重用
// --- 此时 ptr 变成了悬空指针 ---
// 危险操作!
// 虽然 inner_var 的数据可能还没被覆盖(看着是对的),
// 但这块内存已经不属于你了。
printf("Outside: %d\n", *ptr);
return 0;
}
4)realloc 陷阱
realloc(ptr, new_size) 函数用于尝试重新调整ptr所指向的内存块大小,它的执行结果有三种:
原地扩展 、地址搬迁 、分配失败
地址搬迁后的悬空指针
c
#include <stdlib.h>
void dangling_trap_code() {
char *p = (char *)malloc(10);
char *ps = p;
p = (char*)realloc(p, 100);
*ps = 'A';
}
❗❗上述代码中的问题❗❗
-
p和ps一开始都指向0x1000 -
在
p = (char *)realloc(p, 1000);之后此时,
p = 0x2000
ps = 0x1000(旧地址,已释放) -
❗❗危险操作:使用悬空指针
*ps = 'A';对已释放的内存进行写入,导致数据损坏或程序崩溃!
realloc自动释放了0x1000处的旧内存
ps现在指向已被释放的旧内存,成为悬空指针尝试写入已释放的内存块
A-> 💥 崩溃或数据损坏
修复方案
- 总结:
只要对一块内存进行了realloc,指向该内存块内部的所有旧指针(如ps)都必须被视为已失效,不可直接再次使用
c
void dangling_trap_code_fixed() {
char* p = (char*)malloc(sizeof(10));
char* ps = p;
//1. 使用临时变量接收 realloc 的结果,防止返回 NULL 导致内存泄漏
char* temp = (char*)realloc(p, 100);
if (temp != NULL) {
// 3. 【关键修复】更新别名指针 ps
// 因为在这个例子中 ps 原本就指向开头,所以直接 ps = p
ps = p;
*ps = 'A'; // 现在安全了,ps 指向的是有效的新内存
}
free(p); //新的内存分配必须在程序结束前被释放,否则会造成内存泄露
}
5)多个指针指向同一块内存
如果有两个指针 p1 和 p2 指向同一块堆内存,当其中一个指针调用 free(p1) 后,p2 也会立刻变成悬空指针
c
int *p1 = (int *)malloc(sizeof(int));
int *p2 = p1; // p2 和 p1 指向同一位置
free(p1); // p1 释放内存
p1 = NULL;
// p2 此时是悬空指针!试图访问 *p2 会导致崩溃
C/C++如何避免悬空指针
1)C语言
Free and Null原则
释放内存后,必须立即将指针赋值为 NULL
free() 函数只负责回收内存,不会修改指针的值
将其置为 NULL 后,如果再次访问该指针,程序会因为段错误(Segmentation Fault)立即崩溃,而不是产生难以调试的数据损坏(未定义行为)
c
void safe_delete() {
int *ptr = (int *)malloc(sizeof(int));
// ... 使用 ptr ...
free(ptr);
ptr = NULL; // 【关键步骤】防止悬空
}
- 使用安全释放宏
为了避免忘记置空,可以定义一个宏来处理释放操作。这是一种防御性编程技巧
c
#define SAFE_FREE(p) do { if ((p) != NULL) { free(p); (p) = NULL; } } while(0)
int main() {
int *ptr = malloc(sizeof(int));
SAFE_FREE(ptr);
// 此时 ptr 已经是 NULL,再次调用也是安全的
SAFE_FREE(ptr);
return 0;
}
- 避免返回栈内存地址
永远不要返回局部变量(栈内存)的指针。如果需要返回指针,必须在堆上分配 (malloc),或者由调用者传入缓冲区
错误做法:
c
int* bad_func() { int a = 10; return &a; } // 危险
正确做法 (堆分配):
c
int* good_func() {
int *a = malloc(sizeof(int));
*a = 10;
return a;
}
2)C++
- 智能指针
这里我们只是简单概括一下,后面我们将
C++的智能指针的时候会详细展开
使用智能指针避免悬空指针的核心在于:将内存的生命周期与指针对象的生命周期绑定 (RAII技术)
c
#include <memory>
#include <iostream>
using namespace std;
void traditional_problem() {
int* raw_ptr = new int(100); // 分配
// ... 使用指针 ...
delete raw_ptr; // 必须手动释放
// 如果忘记删除 -> 内存泄漏
// 如果提前删除 -> 悬空指针
// 如果删除两次 -> 未定义行为
}
void smart_solution() {
unique_ptr<int> smart_ptr = make_unique<int>(100); // 分配
// ... 使用指针 ...
} // 自动释放!不会忘记,不会提前,不会重复
- 容器代替数组
尽量不要手动分配动态数组(new int[10]),而是使用STL容器
cpp
vector<int> vec(10);
// 离开作用域自动清理,甚至可以用 .at() 进行边界检查

