引用案例自:https://blog.csdn.net/crow_n/article/details/51568848
两个指针指向同一个变量时,删除时容易出问题
逐位复制(bitwise copy) :
众所周知,我们可以把 一个对象实例c1赋值给一个类型与之相同的对象c2(例如 MyClass c2=MyClass c1),在赋值过程中编译器将生成必要的代码把"源"对象各属性的值分别赋值给"目标"对象的对应成员,这种赋值行为被称为逐位复制(bitwise copy)。
逐位复制下的普通变量与指针变量 :
逐位赋值在绝大多数场合都是没有问题的,但如果某些成员变量是指针的话,问题就来了:
对象成员进行逐位复制的结果是你将拥有两个一模一样的实例,这两个实例的普通变量虽然名字一样,但是都有不同的地址(&c1和&c2不同)。而这两个副本实例的同名指针(c1.ptrInt和c2.ptrInt)变量却会指向相同的一个地址…
漏洞:
如果两个实例的同名指针变量指向着同一个地址,当删除其中一个对象,它包含的指针也将被删除,但万一此时另一个副本(对象)还在引用这个指针,便会出现问题!
我们都知道两个指针指向同一个变量时如果一个指针被释放那么另一个就会出问题。如图例:
为了说明问题我做了一个很恶心的小例子
#include <iostream>
#include <string>
class C
{
public :
C(int v)
{
ptrInt=new int;
*ptrInt=v;
valueInt = v;
}
~C()
{
}
void DelIntV()
{
valueInt=0;
delete ptrInt;
}
C(const C& c)
{
}
int * ptrInt;
int valueInt;
private:
};
int main()
{
C c1(2);
C c2(3);
c2=c1;
std::cout<<"c2.ptrInt: "<<c2.ptrInt<<"c2.value "<<*c2.ptrInt<<std::endl;
std::cout<<"c2.valueInt "<<c2.valueInt<<std::endl;
c1.DelIntV();
std::cout<<"\n释放c1后:\nc2.ptrInt:"<<c2.ptrInt<<"c2.value: "<<*c2.ptrInt<<std::endl;
std::cout<<"c2.valueInt: "<<c2.valueInt<<std::endl;
std::cin.get();
return 0;
}
调试过程:
这是把c1赋值给了c2后把指针ptrInt的值输出和valueInt输出,再把c1的指针给delete,valueInt赋值为0,再输出c2的ptrInt和valueInt就会发现指针有问题。
这里记住,此时赋值完以后对象c2里成员指针变量的指向变了!!!
通俗的讲就是创建了c1、c2两个对象,赋值后把c2指到了c1身上。两个对象共用一套器官,c2对c1身体有了控制权,整型变量ValueInt是头发,c1释放以后,也就是c1自杀,器官都死了,c2随同一起死,但是指针释放不影响头发的存在。
为了解决这样的可以重载操作符=
重载操作符的意义在于,把:
(1)c2.ptrInt =c1.ptrInt ;
(2)c2.valueInt = c1.valueInt
两个东西变成:
(1)(*c2.ptrInt )=(*c1.ptrInt), 此处 c2.ptrInt不发生改变,改变的是指针里存放的值;
(2)c2.valueInt = c1.valueInt。
如图例:
C& operator=(const C &c)
{
if(this!=&c)
{
delete ptrInt;
ptrInt = new int;
*ptrInt= *c.ptrInt;
valueInt=c.valueInt;
}
return *this;
}
完整代码
#include <iostream>
#include <string>
class C
{
public :
C(int v)
{
ptrInt=new int;
*ptrInt=v;
valueInt = v;
}
~C()
{
}
void DelIntV()
{
valueInt=0;
delete ptrInt;
}
C(const C& c)
{
}
int * ptrInt;
int valueInt;
C& operator=(const C &c)
{
if(this!=&c)
{
delete ptrInt;
ptrInt = new int;
*ptrInt= *c.ptrInt;
valueInt=c.valueInt;
}
return *this;
}
private:
};
int main()
{
C c1(2);
C c2(3);
c2=c1;
std::cout<<"c2.ptrInt: "<<c2.ptrInt<<"c2.value "<<*c2.ptrInt<<std::endl;
std::cout<<"c2.valueInt "<<c2.valueInt<<std::endl;
c1.DelIntV();
std::cout<<"\n释放c1后:\nc2.ptrInt:"<<c2.ptrInt<<"c2.value: "<<*c2.ptrInt<<std::endl;
std::cout<<"c2.valueInt: "<<c2.valueInt<<std::endl;
std::cin.get();
return 0;
}
重载时调试结果:
释放c1时调试结果
重载的时候构造释放了c2指针指向的内存,然后立马new了一块内存出来,此时c2指针指向的内存位置不变,只是c2指针没有控制权了,new了以后又重新恢复了控制权。
参考以下案例来理解重载操作符里面的两行代码 :
delete ptrInt;
ptrInt = new int;
#include
using namespace std;
void main()
{char*ch1;
ch1=new char(‘h’);
delete ch1;// 只是释放对象的内存空间
*ch1=‘a’;//这里又初始化对象,分配内存
cout<<*ch1<<endl;
}delete是把堆上面的内存空间释放了,也就是告诉系统,这块内存可以被分配给其他人(因为你既然释放内存了,那系统肯定认为你不需要这块内存了),如果有其他人new后得到这块内存,那肯定会被分配给这个人,但是ch1指针仍然指向这块内存(如果你不做ch1=0的话),也可以对其进行任何操作,如果这块内存已经被分配给别人了,那你这时候通过ch1对其进行的操作可能会导致难以预料的严重后果,所以ch1=0很重要。而对于*ch1=‘a’;需要说明,ch1是放在栈上的,而字符‘a’也是在栈上,只是这时候ch1指向了‘a‘在栈上的地址,所以cout<<*ch1<<endl;肯定是输出a。
char*ch1,*ch2; ch1=new char(‘h’); delete ch1;
delete的时候,系统只是将指针指向的堆空间回收,但是没有将指针变量的值赋值为null,也就是说指针还是指向原来的堆空间,但是这个空间已经失效,所以delete一个指针以后要马上将它赋值为null,不然容易导致野指针的出现。