【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
有过C语言编程的朋友大多知道,如果在malloc内存之后不及时free掉内存,那么很有可能会造成内存泄露的。那么在C++上面,是不是也存在这样的问题呢?结果是C++上面同样也存在内存泄露的危险。这个危险就是如果new后面不能delete的话,也会造成内存的泄露。还有不清楚的朋友可以看看下面的示例:
- 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;
- }
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++有没有什么性质可以保证函数在结束的时候可以自动完成资源的释放。对,那就是析构函数。所以,一般的话我们可以添加一个额外的类定义。
- class auto_point
- {
- test* t;
- public:
- auto_point(test* p) : t(p) {}
- ~auto_point() { if(t) delete t;}
- };
class auto_point
{
test* t;
public:
auto_point(test* p) : t(p) {}
~auto_point() { if(t) delete t;}
};
但是,原来我们的好多操作都是按照指针进行的,那么怎么把类转变成指针呢?那就只有使用算术符重载了。
- 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;}
- };
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;}
};
那么有了这样的一个定义之后,我们应该怎么使用呢?大家可以看看下面的函数调用:
- 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: }
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: }
大家可以从上面的代码看得很清楚,不管代码在什么时候退出,函数都会利用类的基本特性,自动调用类的析构函数,那么进而内存就会得到释放,不会发生内存泄露的问题。但是我们发现上面的解决方案也有不足的地方,就是每一个类都需要额外定义一个额外的定义类。那么有没有什么办法解决这一问题呢?那就是模板了。
- 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;}
- };
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++中,如果不注意处理类中的指针,非常容易出问题。如果朋友们不相信可以看看下面的代码:
- 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;
- }
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的汇编代码:
- 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 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处理,这样一旦出现了拷贝操作,编译器就会提示出错。
- 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);
- }
- };
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)编写拷贝构造函数,进行内存深复制
- 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;}
- };
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函数解可以正常编译通过,没有问题。【预告: 下一篇介绍复制运算符中的陷阱】