从汇编的眼光看C++(之delete内存泄露) .(之指针拷贝) .

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】


    有过C语言编程的朋友大多知道,如果在malloc内存之后不及时free掉内存,那么很有可能会造成内存泄露的。那么在C++上面,是不是也存在这样的问题呢?结果是C++上面同样也存在内存泄露的危险。这个危险就是如果new后面不能delete的话,也会造成内存的泄露。还有不清楚的朋友可以看看下面的示例:

  1. class test  
  2. {  
  3.     int data;  
  4. public:  
  5.     test(int value):data(value) {}  
  6.     ~test() {};  
  7. };  
  8.   
  9. void process()  
  10. {  
  11.     test* t = new test(0);  
  12.   
  13.     if(/* some errors happens */)  
  14.     {  
  15.         return;  
  16.     }  
  17.   
  18.     delete t;  
  19.     return;  
  20. }  
class test
{
	int data;
public:
	test(int value):data(value) {}
	~test() {};
};

void process()
{
	test* t = new test(0);

	if(/* some errors happens */)
	{
		return;
	}

	delete t;
	return;
}
    上面的代码在一定程度上说明了问题。其实上面这段代码在函数返回的时候已经考虑到了内存删除的问题,但是关键是如果在error发生的时候,没有及时处理的话也会造成内存泄露的。那么有没有什么好的方法呢?大家可以看看C++有没有什么性质可以保证函数在结束的时候可以自动完成资源的释放。对,那就是析构函数。所以,一般的话我们可以添加一个额外的类定义。

  1. class auto_point  
  2. {  
  3.     test* t;  
  4. public:  
  5.     auto_point(test* p) : t(p) {}  
  6.     ~auto_point() { if(t) delete t;}  
  7. };  
class auto_point
{
	test* t;
public:
	auto_point(test* p) : t(p) {}
	~auto_point() { if(t) delete t;}
};
    但是,原来我们的好多操作都是按照指针进行的,那么怎么把类转变成指针呢?那就只有使用算术符重载了。

  1. class auto_point  
  2. {  
  3.     test* t;  
  4. public:  
  5.     auto_point(test* p) : t(p) {}  
  6.     ~auto_point() { if(t) delete t;}  
  7.     test* operator->() { return t;}  
  8.     const test& operator* () {return *t;}  
  9. };  
class auto_point
{
	test* t;
public:
	auto_point(test* p) : t(p) {}
	~auto_point() { if(t) delete t;}
	test* operator->() { return t;}
	const test& operator* () {return *t;}
};

    那么有了这样的一个定义之后,我们应该怎么使用呢?大家可以看看下面的函数调用:

  1. 22:       auto_point p(new test(0));  
  2. 004010AD   push        4  
  3. 004010AF   call        operator new (00401300)  
  4. 004010B4   add         esp,4  
  5. 004010B7   mov         dword ptr [ebp-18h],eax  
  6. 004010BA   mov         dword ptr [ebp-4],0  
  7. 004010C1   cmp         dword ptr [ebp-18h],0  
  8. 004010C5   je          process+56h (004010d6)  
  9. 004010C7   push        0  
  10. 004010C9   mov         ecx,dword ptr [ebp-18h]  
  11. 004010CC   call        @ILT+80(test::test) (00401055)  
  12. 004010D1   mov         dword ptr [ebp-1Ch],eax  
  13. 004010D4   jmp         process+5Dh (004010dd)  
  14. 004010D6   mov         dword ptr [ebp-1Ch],0  
  15. 004010DD   mov         eax,dword ptr [ebp-1Ch]  
  16. 004010E0   mov         dword ptr [ebp-14h],eax  
  17. 004010E3   mov         dword ptr [ebp-4],0FFFFFFFFh  
  18. 004010EA   mov         ecx,dword ptr [ebp-14h]  
  19. 004010ED   push        ecx  
  20. 004010EE   lea         ecx,[ebp-10h]  
  21. 004010F1   call        @ILT+65(auto_point::auto_point) (00401046)  
  22. 004010F6   mov         dword ptr [ebp-4],1  
  23. 23:       if(1/* some errors happens */)  
  24. 004010FD   mov         edx,1  
  25. 00401102   test        edx,edx  
  26. 00401104   je          process+97h (00401117)  
  27. 24:       {  
  28. 25:           return;  
  29. 00401106   mov         dword ptr [ebp-4],0FFFFFFFFh  
  30. 0040110D   lea         ecx,[ebp-10h]  
  31. 00401110   call        @ILT+75(auto_point::~auto_point) (00401050)  
  32. 00401115   jmp         process+0A6h (00401126)  
  33. 26:       }  
  34. 27:  
  35. 28:       return;  
  36. 00401117   mov         dword ptr [ebp-4],0FFFFFFFFh  
  37. 0040111E   lea         ecx,[ebp-10h]  
  38. 00401121   call        @ILT+75(auto_point::~auto_point) (00401050)  
  39. 29:   }  
22:       auto_point p(new test(0));
004010AD   push        4
004010AF   call        operator new (00401300)
004010B4   add         esp,4
004010B7   mov         dword ptr [ebp-18h],eax
004010BA   mov         dword ptr [ebp-4],0
004010C1   cmp         dword ptr [ebp-18h],0
004010C5   je          process+56h (004010d6)
004010C7   push        0
004010C9   mov         ecx,dword ptr [ebp-18h]
004010CC   call        @ILT+80(test::test) (00401055)
004010D1   mov         dword ptr [ebp-1Ch],eax
004010D4   jmp         process+5Dh (004010dd)
004010D6   mov         dword ptr [ebp-1Ch],0
004010DD   mov         eax,dword ptr [ebp-1Ch]
004010E0   mov         dword ptr [ebp-14h],eax
004010E3   mov         dword ptr [ebp-4],0FFFFFFFFh
004010EA   mov         ecx,dword ptr [ebp-14h]
004010ED   push        ecx
004010EE   lea         ecx,[ebp-10h]
004010F1   call        @ILT+65(auto_point::auto_point) (00401046)
004010F6   mov         dword ptr [ebp-4],1
23:       if(1/* some errors happens */)
004010FD   mov         edx,1
00401102   test        edx,edx
00401104   je          process+97h (00401117)
24:       {
25:           return;
00401106   mov         dword ptr [ebp-4],0FFFFFFFFh
0040110D   lea         ecx,[ebp-10h]
00401110   call        @ILT+75(auto_point::~auto_point) (00401050)
00401115   jmp         process+0A6h (00401126)
26:       }
27:
28:       return;
00401117   mov         dword ptr [ebp-4],0FFFFFFFFh
0040111E   lea         ecx,[ebp-10h]
00401121   call        @ILT+75(auto_point::~auto_point) (00401050)
29:   }
    大家可以从上面的代码看得很清楚,不管代码在什么时候退出,函数都会利用类的基本特性,自动调用类的析构函数,那么进而内存就会得到释放,不会发生内存泄露的问题。但是我们发现上面的解决方案也有不足的地方,就是每一个类都需要额外定义一个额外的定义类。那么有没有什么办法解决这一问题呢?那就是模板了。

  1. template <typename type>  
  2. class auto_point  
  3. {  
  4.     type* t;  
  5. public:  
  6.     auto_point(type* p) : t(p) {}  
  7.     ~auto_point() { if(t) delete t;}  
  8.     test* operator->() { return t;}  
  9.     const test& operator* () {return *t;}  
  10. };  
template <typename type>
class auto_point
{
	type* t;
public:
	auto_point(type* p) : t(p) {}
	~auto_point() { if(t) delete t;}
	test* operator->() { return t;}
	const test& operator* () {return *t;}
};
    如果我们的类型不是特定的,那么我们只需要在使用的时候按照特定的类型输入即可。



【预告: 下面的一篇博客会介绍类成员指针拷贝的问题】

从汇编的眼光看C++(之指针拷贝)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】


    指针是编程人员的梦魇,对C语言的开发者是如此,对C++的开发者也是如此。特别是在C++中,如果不注意处理类中的指针,非常容易出问题。如果朋友们不相信可以看看下面的代码:

  1. class data  
  2. {  
  3.     int* value;  
  4. public:  
  5.     data(int num){  
  6.         if(num > 0)  
  7.             value = (int*)malloc(sizeof(int)* num);  
  8.     }  
  9.   
  10.     ~data(){  
  11.         if(value)  
  12.             free(value);  
  13.     }  
  14. };  
  15.   
  16. void process()  
  17. {  
  18.     data m(10);  
  19.     data p = m;  
  20. }  
class data
{
	int* value;
public:
	data(int num){
		if(num > 0)
			value = (int*)malloc(sizeof(int)* num);
	}

	~data(){
		if(value)
			free(value);
	}
};

void process()
{
	data m(10);
	data p = m;
}
    上面的这段问题有没有什么问题?大家可以自己先用笔在草稿纸上面画一画。然后上机用实际环境验证一下。果不其然,系统提示内存发生了错误。为什么呢?就是因为内存发生了两次释放。我们看以看一下process的汇编代码:

  1. 21:       data m(10);  
  2. 0040105D   push        0Ah  
  3. 0040105F   lea         ecx,[ebp-10h]  
  4. 00401062   call        @ILT+15(data::data) (00401014)  
  5. 00401067   mov         dword ptr [ebp-4],0  
  6. 22:       data p = m;  
  7. 0040106E   mov         eax,dword ptr [ebp-10h]  
  8. 00401071   mov         dword ptr [ebp-14h],eax  
  9. 23:   }  
  10. 00401074   lea         ecx,[ebp-14h]  
  11. 00401077   call        @ILT+5(data::~data) (0040100a)  
  12. 0040107C   mov         dword ptr [ebp-4],0FFFFFFFFh  
  13. 00401083   lea         ecx,[ebp-10h]  
  14. 00401086   call        @ILT+5(data::~data) (0040100a)  
  15. 0040108B   mov         ecx,dword ptr [ebp-0Ch]  
  16. 0040108E   mov         dword ptr fs:[0],ecx  
  17. 00401095   pop         edi  
  18. 00401096   pop         esi  
  19. 00401097   pop         ebx  
  20. 00401098   add         esp,54h  
  21. 0040109B   cmp         ebp,esp  
  22. 0040109D   call        __chkesp (004015b0)  
  23. 004010A2   mov         esp,ebp  
  24. 004010A4   pop         ebp  
  25. 004010A5   ret  
21:       data m(10);
0040105D   push        0Ah
0040105F   lea         ecx,[ebp-10h]
00401062   call        @ILT+15(data::data) (00401014)
00401067   mov         dword ptr [ebp-4],0
22:       data p = m;
0040106E   mov         eax,dword ptr [ebp-10h]
00401071   mov         dword ptr [ebp-14h],eax
23:   }
00401074   lea         ecx,[ebp-14h]
00401077   call        @ILT+5(data::~data) (0040100a)
0040107C   mov         dword ptr [ebp-4],0FFFFFFFFh
00401083   lea         ecx,[ebp-10h]
00401086   call        @ILT+5(data::~data) (0040100a)
0040108B   mov         ecx,dword ptr [ebp-0Ch]
0040108E   mov         dword ptr fs:[0],ecx
00401095   pop         edi
00401096   pop         esi
00401097   pop         ebx
00401098   add         esp,54h
0040109B   cmp         ebp,esp
0040109D   call        __chkesp (004015b0)
004010A2   mov         esp,ebp
004010A4   pop         ebp
004010A5   ret
    21行: data调用构造函数,分配内存给value

    22行: 这里我们发现程序进行内存拷贝,那么表示m变量value的数值和p变量中value的数值是一样的

    23行:这里函数即将结束,所以系统调用m和p的析构函数,第一次析构的时候value指向的内存被释放,第二次析构的时候由于p变量value的数值非0,所以也需要释放内存,当然也需要进行析构处理,但是此时内存已经释放了,所以内存进行了二次释放,系统报错。

 

    经过上面的研究,我们发现了问题和原因,那么应该怎么解决呢?既然问题是在拷贝函数这里,那么就要对拷贝函数进行特殊处理。目前就我个人理解,有两个方法供大家选择:

    (1)对拷贝构造函数进行private处理,这样一旦出现了拷贝操作,编译器就会提示出错。

  1. class data  
  2. {  
  3.     int* value;  
  4.     data(const data&) ;  
  5. public:  
  6.     data(int num){  
  7.         if(num > 0)  
  8.             value = (int*)malloc(sizeof(int)* num);  
  9.     }  
  10.   
  11.     ~data(){  
  12.         if(value)  
  13.             free(value);  
  14.     }  
  15. };  
class data
{
	int* value;
	data(const data&) ;
public:
	data(int num){
		if(num > 0)
			value = (int*)malloc(sizeof(int)* num);
	}

	~data(){
		if(value)
			free(value);
	}
};
    (2)编写拷贝构造函数,进行内存深复制

  1. class data  
  2. {  
  3.     int* value;  
  4.     int number;  
  5. public:  
  6.     data(int num){  
  7.         if(num > 0)  
  8.             value = (int*)malloc(sizeof(int)* num);  
  9.         number = num;  
  10.     }  
  11.   
  12.     data(const data& d){  
  13.         if(NULL != d.get_ptr())  
  14.             value = (int*) malloc(sizeof(int)* d.get_number());  
  15.         number = d.get_number();  
  16.         memmove(value, d.get_ptr(), sizeof(int)* number);  
  17.     }  
  18.   
  19.     ~data(){  
  20.         if(value)  
  21.             free(value);  
  22.     }  
  23.   
  24.     int* get_ptr() constreturn value;}  
  25.     int get_number() const {return number;}  
  26. };  
class data
{
	int* value;
	int number;
public:
	data(int num){
		if(num > 0)
			value = (int*)malloc(sizeof(int)* num);
		number = num;
	}

	data(const data& d){
		if(NULL != d.get_ptr())
			value = (int*) malloc(sizeof(int)* d.get_number());
		number = d.get_number();
		memmove(value, d.get_ptr(), sizeof(int)* number);
	}

	~data(){
		if(value)
			free(value);
	}

	int* get_ptr() const{ return value;}
	int get_number() const {return number;}
};
    我们看到,经过拷贝构造函数的定义后,原来的process函数解可以正常编译通过,没有问题。
    

【预告: 下一篇介绍复制运算符中的陷阱】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值