C++中的悬挂指针和野指针
悬挂指针(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;
}