惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
V
V2EX
博客园 - 【当耐特】
Vercel News
Vercel News
雷峰网
雷峰网
爱范儿
爱范儿
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog
NISL@THU
NISL@THU
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
P
Proofpoint News Feed
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
I
InfoQ
Google Online Security Blog
Google Online Security Blog
L
LINUX DO - 最新话题
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
V
Visual Studio Blog
AI
AI
Schneier on Security
Schneier on Security
B
Blog RSS Feed
T
Tor Project blog
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LINUX DO - 热门话题
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
Arctic Wolf
Webroot Blog
Webroot Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main

博客园 - 新西兰程序员

C++ 构造函数中的初始化参数列表initializer list C++中的线程thread,以及C++11中的std::atomic学习 Leetcode56 Merge Intervals 合并区间 -- C++实现 C++中的内存对齐 C++中的static关键字使用以及全局变量 C++17中的结构化绑定Structured Binding C++中的函数参数是指针类型, 是按值传递 C++中异常处理机制中的栈展开stack unwinding C++实现链表反转 Linux中的性能分析工具 perf 来分析C++性能 C++17中新建一个类时,编译器默认生成的类成员函数 QT中的元对象系统 Leetcode 114 - 二叉树展开为链表 Leetcode65 有效数字 - 判断给定的字符串是否是一个有效数字 LettCode2289-Steps to Make Array Non-descreasing C#中的委托详解 C++中的std::function C++中的仿函数Functor C++中的const和constexpr异同比较 C++中的左值和右值,以及右值引用,移动语义 C++中传递参数是指针类型以及传入参数是指针的指针(**)详解 C++中GetTickCount函数学习 C++中以类的成员函数作为Windows callback函数需要设置成static函数 C#中的System.Security.SecureString学习 C++中四种不同的对象生存方式(in stack, in heap, global, local static) C#中使用Parallel类来进行多线程并发编程 String类型转LPCTSTR -----理解C++中的字符串类型转换 C++中的虚函数和虚函数表 C++中基类指针指向派生类对象 数据结构 - 栈的学习
C++中的悬挂指针和野指针
新西兰程序员 · 2024-04-19 · via 博客园 - 新西兰程序员

悬挂指针(Dangling Pointer), 指的是一个指针它指向已经释放的内存或者无效的内存。当指针指向的内存被释放,这个指针仍然保留着指向之前内存地址的数值,但该地址中的数据已经无效或者被其他数据覆盖

比如一个指针 *Ptr, 它最初指向了一块内存,现在这块内存被释放了,或者这块内存被释放后重新分配给别人,里面是其他的内容。但是这个时候 *Ptr指针依然指向这块内存,

这就会导致问题,它很有可能导致未定义的行为,比如访问无效的内存,程序崩溃或者产生不可预测的结果

 我们来看一个关于悬挂指针的经典的例子:

#include <iostream>

int* CreateInt()
{
int testValue = 1;
int* ptr = &testValue; //指针存储函数内部的局部变量testValue的地址,所以*ptr是一个指向局部变量的指针
return ptr;
}

int main()
{
int* danglingPtr = CreateInt(); //这个时候产生了悬挂指针

//尝试访问悬挂指针指向的内存
std::cout << *danglingPtr << std::end1; //这条语句很有可能导致未定义的未知行为

return 0;
}

 在上面这段代码中, int* danglingPtr = CreateInt();  => CreateInt()方法会返回一个int类型的指针,但是这个指针指向的是CreateInt()方法内部的局部变量testValue. 当我们用CreateInt()执行完这个方法并返回int类型的指针后,它里面的局部变量也就完成了生命周期,内存被释放了。但这个时候,悬挂指针*danglingPtr仍然指向了已经释放了的这块内存的地址。 所以,当我们试图去访问悬挂指针所指向的内存时,就很有可能会产生未定义的行为

为了避免悬挂指针的问题,我们在使用指针时应该注意以下几点:

1.  避免返回指向局部变量的指针

2. 在释放内存后,将指针置为nullPtr, 或者重新为这个指针分配其他有效的内存

3.  在使用指针前,始终都应该检查指针是否为有效指针

上面的例子,返回了指向函数的局部变量的指针,导致了悬挂指针的出现。这其实只是悬挂指针出现的一种情况, 除此之外,还有2种情况,可能会导致悬挂指针的出现

第一种

使用delete语句指针后,该指针没有再次赋值之前,此时这个指针是悬挂指针。 说到这里,我们首先需要理解一下 delete 指针到底是做了些什么

我们知道,在动态分配内存时,我们可以使用new在动态内存中为对象分配空间并返回一个指向该对象的指针, 比如说 int* ptr = new int(1); 为了防止内存耗尽,在这个动态内存使用完毕后,我们必须将其归还给系统,这个时候我们是通过delete语句来讲动态内存归还给系统的 => delete ptr. 那么,我们真正理解了delete ptr是干什么的么

delete ptr => 实际上是删除了ptr所指的目标 ,释放它(ptr所指的目标)所占的堆空间。 而不是删除指针ptr本身 (也就是说,指针ptr本身并没有被删除,它自己仍然存在,自然,该指针所占用的内存空间也并没有被释放)。

所以,delete ptr => 之后, ptr就成了 "空悬指针" => 即ptr此时指向一块已经被回收的内存地址. 这个时候,ptr会在内存里乱指一通,有可能指到一些重要地址造成出错。 为了使用的安全,我们一般在delete ptr之后还会加上 ptr = nullptr; => 这条语句会使其不指向任何的对象。 我们来看一个例子:

#include<iostream>
#include<memory>
using namespace std;

int main(){
  int* ptr = new int(100);
cout << *ptr << end1; //ptr指向的对象
cout << ptr << end1; //ptr指向对象的地址
cout << &ptr << end1; // ptr的地址
delete ptr;

cout << &ptr << end1; //delete后ptr的地址不变
cout << ptr << end1; //delete后ptr指向对象的地址变随机 return 0; }

我们来看看执行的结果

 第二种  超出变量的作用范围

#include<iostream>
using namesapce std;
int main()
{
     int *p;
     {
           int temp = 10;
           p = &temp;

     }

     //p在此处是悬挂指针

    return 0;
}

接下来,我们来说说什么是野指针

野指针 =》 野指针是指尚未初始化的指针,也就是说声明了一个指针,但是还没有初始化。 此时它指向的地址是未知的,不确定的,随机的。 我们来看一个例子

#include<iostream>
using namesapce std;
int main()
{
int* p1; //此时声明了,但没有初始化指针p1, 这个时候它就是野指针
cout << "*p1 = " << *p1 << end1; //指针*p1 未初始化就被使用
delete p1;
return 0; }

这样使用时,编译器会直接报错,  产生非法的内存访问

那么如何来避免这种野指针的情况呢,我们可以把指针初始化为NULL,如下

#include<iostream>
using namespace std;
int main()
{
int* p1; //此时p1属于野指针
int* p2 = NULL; // 此时p2不属于野指针,因为给它赋予了初始值NULL
p1 = new int(10); //此时p1就不再是野指针了
p2 = new int(10);
cout << "*p1 = " << *p1 << end1;
cout << "*p2 = " << *p2 << end1;
delete p1;
delete p2;
return 0;

}