c++中的常见泄漏(一)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-14。
*出处:http://blog.csdn.net/zsm0107。
-----------------------------------------------------------------------------
在类的构造函数和析构函数中没有匹配的调用new和delete表达式。
详细:
用new动态分配了内存,却忘了调用delete显示的释放。
1.在堆里创建了对象占用内存,但是没有显示的释放对象占用的内存
2.在累的析构函数中动态的分配了内存,但是在析构函数中或是没有释放内存或是没有正确的释放内存
例子
class Point
{
private:
int x;
int y;
char *color;
public:
Point(int,int,char *);
~Point();
};
//注意:析构函数为数据成员"color"分配了内存空间,但是没有析构函数来释放这一分配的内存。
Point::Point(int new_x, int new_y, char *col)
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
}
Point::~Point()
{
delete color;
}
c++中的常见泄漏(二)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-15。
*出处:http://blog.csdn.net/Zsm0107。
-----------------------------------------------------------------------------------
没有正确的清除嵌套的对象指针
详细:
一个对象以引用的方式包含了另一个对象,而不是用值的方式。只有一个对象以值方式包含另一个对象的时候才会自动的调用另一个对象的析构函数。
例子:
#include<iostream>
#include<string>
using namespace std;
class Melon
{
private:
char *variety;
public:
Melon(char *var);
~Melon();
void print();
};
Melon::Melon(char *var)
{
variety=new char[strlen(var)+1];
strcpy(variety,var);
}
Melon::~Melon()
{
delete variety;
}
void Melon::print()
{
cout<<"I'm a "<<variety<<"Melon"<<endl;
}
class Meal
{
private:
char *restaurant;
Melon *m;
public:
Meal(char *var , char *res);
~Meal();
void print();
};
Meal::Meal(char *var , char *res)
{
m=new Melon(var);
restaurant=new char[strlen(res)+1];
strcpy(restaurant , res);
}
//哎呀,没有释放甜瓜。如果甜瓜是作为一个被包含的对象的话,就不会是这样子了。
Meal::~Meal()
{
delete restaurant;
}
//正确的析构函数如下:
/*
Meal ::~Meal()
{
delete restaurant;
delete m;
}
*/
void Meal::print()
{
cout<<"I'm a Meal owed by";
m->print();
}
int main()
{
Meal m1("Honeydew" ,"Four Seasons ");
Meal m2("Cantaloup" ,"Brook Manor Pub");
m1.print();
m2.print();
return 0;
}
c++中的常见泄漏(三)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-15。
*出处:http://blog.csdn.net/Zsm0107。
---------------------------------------------------------------------------
在释放对象数组时在delete中没有使用方括号
详细:
delete函数中方括号的使用,比如说,delete [] p 和 delete p 是不同的。
1.释放单个对象、单个基本数据类型(例如:int,char,short)的变量或是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要这个参数。
2.方括号是告诉编译器这个指针指向的是一个对象向量,同时也告诉编译器正确的对象的地址值并调用对象的析构函数。如果没有方括号,那么这个指针就是默认为是只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果就造成了内存泄漏。
3.如果在方括号中间放了一个比对象数组的大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的崩溃。
4.如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个的析构函数,结构会造成内存泄漏。
注意:在所有的因为缺少方括号而造成的内存泄漏的情况中,类占用的内存(大小为sizeof(class name))会被放回堆中,这是由new和delete的性质决定的。
例子:
//此代码演示了与释放动态分配的Point对象数组相关的内存泄漏问题
#include<iostream>
#include<string>
using namespace std;
class Point
{
private:
int x;
int y;
char *color;;
public:
Point(int new_x=0 , int new_y=0 , char *col="Red");
~Point();
};
Point::Point(int new_x=0 , int new_y=0 , char *col="Red")
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
}
Point::~Point()
{
delete color;
cout<<"In the destructor"<<endl;
}
int main()
{
Point *p=new Point[5]; //注意这里是新建5个对象
// delete p;
/*注意,这里缺少方括号。没有了方括号,这一语句就等同于 delete [1] p 。编译器将只以&p[0]的地址作为参数调用一次析构函数。*/
//正确的是:
delete [] p; //或是 delete [5] p;
}
c++中的常见泄漏(四)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-16。
*出处:http://blog.csdn.net/Zsm0107。
---------------------------------------------------------------------------
指向对象的指针数组不同于对象数组
详细:
释放对象指针数组和释放对象数组的区别
1.在释放对象数组时,编译器用delete函数(不是delete函数而是delete表达式(感谢网友chu009的指正)-:))中的方括号来控制正确的调用每一个对象的析构函数
2.若用大小参数来控制正确的调用存储于指针数组中的对象的析构函数是没有用的。因为指针是一种基本数据类型,所以这个大小参数对于析构函数调用是没有用的,所以指针被收回堆里了,但是所有的对象没有被释放,结果就造成了内存泄漏。
例子:
#include<iostream>
#include<cstring>
using namespace std;
class Point
{
private:
int x;
int y;
char *color;;
public:
Point(int new_x , int new_y , char *col);
void show()
{
cout<<x<<"/n"
<<y<<"/n"
<<color<<endl;
}
~Point();
};
Point::Point(int new_x, int new_y , char *col)
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
}
Point ::~Point()
{
delete color;
}
int main()
{
Point **p=new Point *[10]; //动态的分配了指向10个Point对象的指针数组(他们本身不是对象)
for(int i=0;i!=10;++i)
{
p[i]=new Point(i,i,"Green");
p[i]->show();
}
// delete [] p; //或是delete [10] p;
/*注意这条语句,它没有释放Point对象,释放的只是它们的指针。结果造成了内存泄漏(sizeof(Point)+60字节的空间)。其中的60个字节是10个对象中的"Green"字符串占用的。*/
//正确的如下:
for(int j=0;j!=10;++j)
{
delete p[j];
}
delete p; //或是delete [] p;
return 0;
}
c++中的常见泄漏(五)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-17。
*出处:http://blog.csdn.net/Zsm0107。
---------------------------------------------------------------------------
缺少拷贝构造函数
详细:
1.拷贝构造函数是以同类型对象的引用为参数的构造函数。
2.如果一个对象赋给一个新的对象时,类中没有定义拷贝拷贝构造函数,编译器就会认为是以逐个成员拷贝的方式来复制数据成员。问题是:
逐个成员拷贝的方式复制指针被定义为讲一个变量的地址复制给另一个变量。这种隐式复制的结果是了两个对象拥有指向同一个人动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数会释放与该对象有关的动态分配内存空间。而释放第二个对象的时候,它的析构函数将会再次释放相同的内存,所以可能造成堆的崩溃。
3.隐式调用:当一个对象用同类型的对象来初始化时,会调用拷贝构造函数。或是用一个对象以值传递方式作为拷贝构造函数的参数,或是从一个函数中返回一个对象时都会调用拷贝构造函数。
例子:
#include<iostream>
#include<string>
using namespace std;
class Point
{
private:
int x;
int y;
char *color;
public:
Point(int new_x , int new_y , char *col);
//注意下面的拷贝构造函数,若缺失的有什么后果呢???:-)
Point(const Point& rhs);
~Point();
Point duplicate(Point rhs);
void print();
};
Point::Point(int new_x=0 , int new_y=0 , char *col="white")
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
cout<<"call constructor"<<endl;
}
Point::Point(const Point& rhs)
{
x=rhs.x;
y=rhs.y;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
cout<<"call copy constructor"<<endl;
}
Point::~Point()
{
delete color;
cout<<"call ~Point()"<<endl;
}
/*这个函数以值方式将一个Point对象作为函数的参数。这将造成隐式的调用拷贝构造函数。另外,因为这个函数以值方式返回一个Point对象,所以将隐式调用拷贝构造函数*/
Point Point::duplicate(Point rhs)
{
x=rhs.x;
y=rhs.y;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
cout<<"call duplicate function"<<endl;
return (*this);
}
void Point::print()
{
cout<<"I'm a point at ( "
<<x<<" , "<<y<<" ) "<<endl
<<"My color is: "<<color<<"."
<<endl;
}
int main()
{
Point p1(10,10,"Blue");
Point p2(15,18,"white");
Point p3=p2; //隐式调用拷贝构造函数
p1.print();
p2.print();
p3.print();
p1.duplicate(p2); //两次隐式调用拷贝构造函数
p1.print();
p2.print();
p3.print();
return 0;
}
缺少重载赋值运算符
详细:
当赋值运算符左边的对象的内部地址和赋值右边运算符右边的对象的内部地址重叠时,内存泄露就发生了---左边的对象中的指针指向的内存已不再被引用,但是这块内存仍然没有释放。另外两个操作数对象指向了同一块动态分配的内存空间,当两个对象调用了析构函数释放同一块内存时就有可能造成堆得崩溃。
例子:
#include<iostream>
#include<string>
using namespace std;
class Point
{
private:
int x;
int y;
char *color;
public:
Point(int new_x , int new_y , char *col);
//注意下面的拷贝构造函数,若缺失的有什么后果呢???:-)
Point(const Point& rhs);
const Point& operator=(const Point& rhs);
~Point();
void print();
};
Point::Point(int new_x=0 , int new_y=0 , char *col="white")
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
cout<<"call constructor"<<endl;
}
Point::Point(const Point& rhs)
{
x=rhs.x;
y=rhs.y;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
cout<<"call copy constructor"<<endl;
}
//这个函数允许将对象本身赋值给自己。将返回引用定义成const,使得重载的
//operate=返回值不能作为一个左值来使用。
const Point& Point::operator=(const Point& rhs)
{
if(&rhs==this)
{
return (*this);
}
x=rhs.x;
y=rhs.y;
delete color;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
cout<<"call operator="<<endl;
return (*this);
}
Point::~Point()
{
delete color;
cout<<"call ~Point()"<<endl;
}
void Point::print()
{
cout<<"I'm a point at ( "
<<x<<" , "<<y<<" ) "<<endl
<<"My color is: "<<color<<"."
<<endl;
}
int main()
{
Point p1(10,10,"Blue");
Point p2(15,18,"white");
Point p3=p2; //隐式调用拷贝构造函数
p1.print();
p2.print();
p3.print();
p1=p2;
p1.print();
p2.print();
return 0;
}
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-19。
*出处:http://blog.csdn.net/Zsm0107。
--------------------------------------------------------------------------
关于nonmodifying运算符重载的常见迷思
详细:
1.概念:所谓nonmodifying运算符就是不改变操作数的值,并且计算结果是一个与操作同类型的对象的运算符。比如数学运算符:+,-,*,/,%.而关系运算符(bool类型)和赋值运算符(改变左边的操作数)则不是nonmodifying运算符。
2.与nonmodifying运算符有关的主要问题是返回值。为了提高效率节省时间,决定用引用方式来返回运算结果,而不是返回值方式(隐式调用拷贝构造函数)。返回引用相关的临时变量时,会出现内存泄漏问题。(用引用方式返回局部变量是错误的----当函数退出时,与这个引用相关的内存也就退回了栈。从而使函数调用者得到一个指向无效的、没有分配到内存的对象)
例子:
#include<iostream>
#include<string>
using namespace std;
class Point
{
private:
int x;
int y;
char *color;
public:
Point(int =0 , int =0 , char * ="white");
~Point();
Point(const Point&);
void print();
const Point& operator=(const Point&);
const Point& operator+(const Point&);
//正确的是: const Point operator+(const Point&);
};
Point::Point(int new_x , int new_y , char *col)
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
}
Point::~Point()
{
delete color;
}
Point::Point(const Point& rhs)
{
x=rhs.x;
y=rhs.y;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
}
const Point& Point::operator=(const Point& rhs)
{
if(&rhs==this)
return (*this);
x=rhs.x;
y=rhs.y;
delete color;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
return (*this);
}
//此函数返回一个指向临时Point对象的隐指针(一个引用),这个指针在函数退出时就被释放啦
const Point& Point::operator+(const Point& rhs)
{
Point temp;
temp.x=x+rhs.x;
temp.y=y+rhs.y;
delete temp.color;
temp.color=new char[strlen(color)+strlen(rhs.color)+1];
sprintf(temp.color,"%s%s",color,rhs.color);
return (temp);
//waring:returning address of local variable or temporary
}
void Point::print()
{
cout<<"I live at ( "
<<x<<" , "<<y<<" ) and my color is :"
<<color<<endl;
}
int main()
{
Point p1(10,10,"Blue");
Point p2(20,60,"Green");
Point p3=p1+p2;
/*拷贝构造函数的参数是一个已经被释放的Point对象,因为运算符+重载函数返回的是指向自动变量temp的隐指针。这个变量在退出运算符重载函数时就调用了它的析构函数*/
p3.print();
return 0;
}
*------------------------------------------------------------------------
*解决问题的一种方法是:将临时的Point对象作为类的内部静态存储对象。内部 *静态对象不会在进入/退出函数的时候被创建/释放,它只是进入/退出作用域。*但是对于嵌套调用(比如:x+y+z)就会出现问题啦
*----------------------------------------------------------------------*
//修改代码如下:
const Point& Point::operator+(const Point& rhs)
{
static Point temp;
//注意内部静态存储对象temp的使用。每一次调用这个函数时读和写的对象时
//同一个。
temp.x=x+rhs.x;
temp.y=y+rhs.y;
delete temp.color;
temp.color=new char[strlen(color)+strlen(rhs.color)+1];
sprintf(temp.color,"%s%s",color,rhs.color);
return (temp);
}
*-------------------------------------------------------------------------
*还有一种思路是利用动态分配来解决,但是由于动态分配的临时变量的析构函
*数并没有自动的被调用,即它们没有被收回。此外得不到对象的地址,函数的
*调用者也不能显示的调用析构函数。
*-----------------------------------------------------------------------*
//代码如下:
const Point& Point::operator+(const Point& rhs)
{
Point *temp=new Point;
delete temp->color;
temp.x=x+rhs.x;
temp.y=y+rhs.y;
delete temp.color;
temp.color=new char[strlen(color)+strlen(rhs.color)+1];
sprintf(temp.color,"%s%s",color,rhs.color);
return (*temp);
}
*-------------------------------------------------------------------------
综上所述:
nonmodifying运算符返回的必须是一个对象,而不是一个对象的引用。
const Point Point::operator+(const Point& rhs)
{
Point temp;
temp.x=x+rhs.x;
temp.y=y+rhs.y;
delete temp.color;
temp.color=new char[strlen(color)+strlen(rhs.color)+1];
sprintf(temp.color,"%s%s",color,rhs.color);
return (temp);
}
*-----------------------------------------------------------------------*
c++中的常见泄漏(二)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-15。
*出处:http://blog.csdn.net/Zsm0107。
-----------------------------------------------------------------------------------
没有正确的清除嵌套的对象指针
详细:
一个对象以引用的方式包含了另一个对象,而不是用值的方式。只有一个对象以值方式包含另一个对象的时候才会自动的调用另一个对象的析构函数。
例子:
#include<iostream>
#include<string>
using namespace std;
class Melon
{
private:
char *variety;
public:
Melon(char *var);
~Melon();
void print();
};
Melon::Melon(char *var)
{
variety=new char[strlen(var)+1];
strcpy(variety,var);
}
Melon::~Melon()
{
delete variety;
}
void Melon::print()
{
cout<<"I'm a "<<variety<<"Melon"<<endl;
}
class Meal
{
private:
char *restaurant;
Melon *m;
public:
Meal(char *var , char *res);
~Meal();
void print();
};
Meal::Meal(char *var , char *res)
{
m=new Melon(var);
restaurant=new char[strlen(res)+1];
strcpy(restaurant , res);
}
//哎呀,没有释放甜瓜。如果甜瓜是作为一个被包含的对象的话,就不会是这样子了。
Meal::~Meal()
{
delete restaurant;
}
//正确的析构函数如下:
/*
Meal ::~Meal()
{
delete restaurant;
delete m;
}
*/
void Meal::print()
{
cout<<"I'm a Meal owed by";
m->print();
}
int main()
{
Meal m1("Honeydew" ,"Four Seasons ");
Meal m2("Cantaloup" ,"Brook Manor Pub");
m1.print();
m2.print();
return 0;
}
c++中的常见泄漏(三)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-15。
*出处:http://blog.csdn.net/Zsm0107。
---------------------------------------------------------------------------
在释放对象数组时在delete中没有使用方括号
详细:
delete函数中方括号的使用,比如说,delete [] p 和 delete p 是不同的。
1.释放单个对象、单个基本数据类型(例如:int,char,short)的变量或是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要这个参数。
2.方括号是告诉编译器这个指针指向的是一个对象向量,同时也告诉编译器正确的对象的地址值并调用对象的析构函数。如果没有方括号,那么这个指针就是默认为是只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果就造成了内存泄漏。
3.如果在方括号中间放了一个比对象数组的大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的崩溃。
4.如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个的析构函数,结构会造成内存泄漏。
注意:在所有的因为缺少方括号而造成的内存泄漏的情况中,类占用的内存(大小为sizeof(class name))会被放回堆中,这是由new和delete的性质决定的。
例子:
//此代码演示了与释放动态分配的Point对象数组相关的内存泄漏问题
#include<iostream>
#include<string>
using namespace std;
class Point
{
private:
int x;
int y;
char *color;;
public:
Point(int new_x=0 , int new_y=0 , char *col="Red");
~Point();
};
Point::Point(int new_x=0 , int new_y=0 , char *col="Red")
{
x=new_x;
y=new_y;
color=new char[strlen(col)+1];
strcpy(color,col);
}
Point::~Point()
{
delete color;
cout<<"In the destructor"<<endl;
}
int main()
{
Point *p=new Point[5]; //注意这里是新建5个对象
// delete p;
/*注意,这里缺少方括号。没有了方括号,这一语句就等同于 delete [1] p 。编译器将只以&p[0]的地址作为参数调用一次析构函数。*/
//正确的是:
delete [] p; //或是 delete [5] p;
}
c++中的常见泄漏(八)
--------------------------------------------------------------------------
*整理:Zsm。
*时间:2011-4-12。
*出处:http://blog.csdn.net/Zsm0107。
--------------------------------------------------------------------------
没有将基类的析构函数定义为虚函数
详细:
如果任何的最终派生类含有指向动态分配的内存的指针,并且基类的析构函数不是虚函数,那么就会造成内存泄漏,因为最终派生类的析构函数不会被调用。说白了就是动态绑定问题。
例子:
#include<iostream>
#include<string>
const int N=4;
using namespace std;
class Fruit
{
private:
double weight;
char *color;
protected:
Fruit(double , char *);
Fruit(const Fruit&);
public:
virtual ~Fruit(); //error:~Fruit()
virtual void print();
};
Fruit::Fruit(double w , char *col)
{
weight=w;
color=new char[strlen(col)+1];
strcpy(color,col);
cout<<"call Fruit(double,char *)"<<endl;
}
Fruit::Fruit(const Fruit& rhs)
{
weight=rhs.weight;
color=new char[strlen(rhs.color)+1];
strcpy(color,rhs.color);
cout<<"call Fruit(const Fruit&)"<<endl;
}
Fruit::~Fruit()
{
delete color;
cout<<"call ~Fruit()"<<endl;
}
void Fruit::print()
{
cout<<"My weight: "<<weight<<endl
<<"My color: "<<color<<endl;
}
class Apple:public Fruit
{
private:
char *variety;
public:
Apple(double , char * , char *);
Apple(const Apple&);
~Apple();
void print();
};
Apple::Apple(double w , char *col ,char *var):Fruit(w,col)
{
variety=new char[strlen(var)+1];
strcpy(variety,var);
cout<<"call Apple(double,char *,char *)"<<endl;
}
Apple::Apple(const Apple& rhs):Fruit(rhs)
{
variety=new char[strlen(rhs.variety)+1];
strcpy(variety,rhs.variety);
cout<<"call Apple(const Apple&)"<<endl;
}
Apple::~Apple()
{
delete variety;
cout<<"call ~Apple()"<<endl;
}
void Apple::print()
{
cout<<"Hi,I'm a "<<variety<<" Apple "<<endl;
Fruit::print();
}
class Banana:public Fruit
{
private:
char *export;
public:
Banana(double , char * , char *);
Banana(const Banana&);
~Banana();
void print();
};
Banana::Banana(double w , char *col ,char *exp):Fruit(w,col)
{
export=new char[strlen(exp)+1];
strcpy(export,exp);
cout<<"call Banana(double,char *,char *)"<<endl;
}
Banana::Banana(const Banana& rhs):Fruit(rhs)
{
export=new char[strlen(rhs.export)+1];
strcpy(export,rhs.export);
cout<<"call Banana(const Banana&)"<<endl;
}
Banana::~Banana()
{
delete export;
cout<<"call ~Banana()"<<endl;
}
void Banana::print()
{
cout<<"Hi,I'm a Banana from "<<export<<endl;
Fruit::print();
}
int main()
{
Fruit *basket[N];
int num=0;
double w;
char col[128]="red";
char var[128]="Asia";
char exp[128]="China";
char answer[128];
//下面的代码可以让用户交互式的创建元素个数为1~N的水果数组。
cout<<"How many fruit in the basket?";
cin>>num;
if(num<0||num>N)
num=N/2;
for(int i=0;i!=num;++i)
{
cout<<"A)pple or B)anana?";
cin>>answer;
if('a'==answer[0]||'A'==answer[0])
{
cout<<"Apple's weight:";
cin>>w;
cout<<"Apple's color:";
cin>>col;
cout<<"Apple's varitey:";
cin>>var;
basket[i]=new Apple(w,col,var);
}
else
{
cout<<"Banana's weight:";
cin>>w;
cout<<"Banana's color:";
cin>>col;
cout<<"Banana's country:";
cin>>exp;
basket[i]=new Banana(w,col,exp);
}
}
for(int j=0;j!=num;++j)
{
basket[j]->print();
}
for(int k=0;k!=num;++k)
{
delete basket[k];
}
//如果水果的析构函数不是虚函数,那么上面的代码就会调用水果的析构函数num次。结果就是内存泄漏。
return 0;
}