【一天一个计算机知识】—— 【编程百度】悬空指针


点击下面查看作者专栏 🔥🔥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所指向的内存块大小,它的执行结果有三种:
原地扩展地址搬迁分配失败

Lucy的空间骇客裂缝:realloc解析


地址搬迁后的悬空指针

c 复制代码
#include <stdlib.h>

void dangling_trap_code() {
    char *p = (char *)malloc(10);
    char *ps = p;
	
	p = (char*)realloc(p, 100);

	*ps = 'A';
	}

❗❗上述代码中的问题❗❗

  • pps一开始都指向 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)多个指针指向同一块内存

如果有两个指针 p1p2 指向同一块堆内存,当其中一个指针调用 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() 进行边界检查

希望读者多多三连

给小编一些动力

蟹蟹啦!

蟹蟹啦!

相关推荐
吃着火锅x唱着歌1 小时前
LeetCode 624.数组列表中的最大距离
数据结构·算法·leetcode
矜辰所致1 小时前
C 语言 —— 函数指针
c语言·开发语言·指针·typedef·函数指针
zore_c1 小时前
【C语言】struct结构体内存对齐和位段(超详解)
c语言·开发语言·经验分享·笔记
Chrikk1 小时前
【下篇】C++20 约束、NCCL 通信与并发模型
c++·c++20·c++40周年
MC皮蛋侠客1 小时前
C++17多线程编程全面指南
开发语言·c++
wulaladamowang1 小时前
github国内资源下载
github
獭.獭.1 小时前
C++ -- STL【vector的使用】
c++·stl·vector
fei_sun1 小时前
【数据结构】2019年真题
数据结构
郝学胜-神的一滴1 小时前
Linux C++系统编程:使用mmap创建匿名映射区
linux·服务器·开发语言·c++·程序人生