动态内存管理

堆区(动态内存)

当执行new运算符时,系统会自动在动态内存空间中分配存储。动态内存是唯一一个生存期可以由程序员自己控制的存储空间。程序在运行时程序员可用new申请动态内存空间,但不用时,程序员必须自己delete这部分空间(释放内存)。因此,程序在这部分出错的概率极高。程序员管理动态内存空间的运算符是new和delete,还有new[ ]和[ ]delete。

栈区

调用函数时,函数内的局部变量和形式参数等将在栈上分配存储单元。这部分变量的生命周期与函数的执行时间相同。当函数执行结束时,存储这些变量的存储单元会被自动释放,从而这些变量的生命周期也就完结了。由于栈的大小一般很有限,因此能够同时保存在栈上的变量数量有限。但存储在栈上的变量生命周期短,因此栈的使用效率很高,可以不断成为新生成变量的存放空间。

静态数据区

在静态数据区中分配的变量或对象在该程序的整个运行期间都存在。它们的生命周期贯穿整个程序的运行周期。比如:全局变量,static变量等就存储在静态数据区。

new

new运算符能够自动计算所申请的空间大小,而malloc( )函数则必须由程序员指出所需申请分配的空间大小。所以new运算符比malloc( )函数的功能要强。C++选择使用new和delete运算符。
<指针名> = new <类型>( <实参表> );

int *p1,*p2, (*p3)[3];
p1 = new int;		//new一个整型数,并返回该整型数的地址赋值给p1
p2 = new int( 200 );	//new一个初值为200的整型数,并返回该整型数的地址赋值给p2
p3 = new int[2][3];    //new一个关于一维数组的数组(元素具有三个整型元素的一维数组)
CNode *poCN1 = new CNode[LEN]; 	//new一个对象数组,返回首地址赋值给poCN1
CNode *poCN2 = new CNode( 5 );		//new一个对象,并将其首地址赋值给poCN2

new生成一个对象数组时是不能够带参数,即生成对象数组时只能调用无参构造函数或者全部参数都有缺省值的构造函数。此时,有两种方法对对象数组中的对象元素进行初始化:
1、不定义构造函数,而在类中定义一个成员函数完成初始化工作。
2、在类中定义不带参数或带缺省参数的构造函数。
如果在类声明中定义了构造函数,但没有定义不带参数的构造函数,且没有定义带全部缺省参数的构造函数,那么该类将不能用new申请对象数组。

delete

delete <指针名>; 	//释放一般对象或者变量
delete [ ] <指针名>; 	//释放整个数组, [ ]内无须有值,delete知道数组的大小

delete只能用来释放用new申请分配的动态内存空间,new的动态内存空间必须用delete来释放。new和delete必须有对应关系。下面的delete即是上面对应的new运算符的delete:

delete p1;		//释放一个int空间
delete p2;		//释放一个int空间
delete [ ] p3;	//释放用new申请的整型数组空间,对多维空数组的释放格式与一维数组相同
delete [ ] poCN1;		//释放由new申请的对象数组空间。
delete poCN2;		//释放一个对象空间

内存泄漏

执行 CNode *poCN2 = new CNode( 5 );语句时,内存中会生成两个东西,一个是对象指针变量,另一个是在动态内存空间中的对象。而指针变量的值,就是这个新创建的、位于动态内存空间的对象的首地址,也就是动态内存地址。动态内存空间中的对象是没有名字的。只有通过这个指针变量才可以访问它。在该对象没有销毁以前,不能随便改变这个指针的值。否则就没有办法找到该对象,对象所占据的那一片动态内存空间也将无法回收,从而造成内存泄漏。内存泄漏多指这种动态内存的泄漏。

delete执行以后,指针所指向的内存空间就被释放了。所以delete的作用是把指针所指向的动态内存空间释放掉。而指向该动态内存区域的指针变量本身并不会因为delete有任何改变。即delete以后,指针依然指向其原动态内存空间,指针变量的值并未改变。改变的只是指针所指向的动态内存空间的内容。这部分动态内存空间已经被释放,该空间存放的内容已经不复存在,变得毫无意义,变成了垃圾(对象已经不存在了)。被释放掉的动态内存空间随时有可能被分配给别的任何变量。所以此后通过指针变量对该动态内存的任何操作都有可能引发系统奔溃。此时用if ( p != NULL )进行防错处理将毫无意义,因为p的值并未改变。所以delete以后,一般会选择将指针变量的值设置为NULL,让它不再指向任何动态内存空间,以避免通过该指针变量引发灾难操作。
如果new和delete使用不当,将可能出现不可预测的结果,这种情况可能导致“内存泄漏”。如delete以后不对指针变量做任何处理,就会造成“指针悬挂”。如指针变量声明后没有初始化就通过它操作内存空间,就有可能制造了一个“野指针”。它们对程序的安全都是一种严重的威胁。可从以下几点入手预防:
1、定义指针变量的同时初始化。如不初始化就一定将其设置成NULL,避免该指针指向一个不确定的地方,引发误操作,这是非常危险的。一旦把它置成NULL,误用它也不会造成太大问题。
2、delete指针以后,第一时间将其设置为NULL。即使是一个马上就要消失的局部指针变量,也可立即将其置为NULL。养成一种好的编程习惯是避免错误的高效方法。
3、当指针指向数组时,一定谨防指针操作越界。在计算for、while等循环的循环次数、设计循环边界时,尤其要小心。
4、避免用指针传递栈内存、避免返回一个即将自动消失的局部变量或局部对象的地址。栈内存中的局部变量和局部对象不能跨函数生存。要随时注意指针变量本身以及指针所指向的对象的生存周期。
5、注意区分指针变量和指针指向的内存空间之间的差异:指针变量p和它所指向的内存空间是两个不同的东西,这两者是完全不同的,它们具有不同的特性,具有不同的内存空间,具有不同地址。尤其面对“char *pChar = new char[100]”这样的语句,一定要提高警惕。
比如:

void Func( void )
{
	char *pChar = new char[100];	// 在退出函数时,pChar和动态内存会被自动释放吗?
} 
// 当调用Func( )函数时,会在动态内存中创建一个char型数组,并用pChar指向该数组。
// 当Func( )调用结束时,pChar会自动消失,因为它位于栈区中,是局部变量。
// 而长度为100的动态内存空间中的字符串数组却依然存在,没有释放。
// 所以退出Func( )后,内存就泄漏了。而且每次调用都会造成新的泄漏。

6、关于指针和动态内存要特别注意理解以下两点:指针消亡了,并不表示它所指向的动态内存会被自动释放、自动消亡;动态内存被释放了,并不表示指向该动态内存的指针变量会消亡或自动变成NULL指针。

//文件名:s8_1\smain8_1.cpp
//new和delete的使用

#include <iostream>
#include <string>
using namespace std;
const int LEN = 5;				//定义一个常量

class CNode					//声明CNode类
{
public:
	CNode( int value=0 );			//构造函数,缺省参数value=0
	void Print( ) const;

private:
	int m_value;				//节点值
};

CNode::CNode( int value ) : m_value( value )	//构造函数
{
	;
}

void CNode::Print( ) const			//显示,常成员函数
{ 
	cout<< "CNode value : " << m_value << endl; 
}

//测试函数。
void main( )
{
	CNode *poCN = new CNode[LEN];	//创建对象数组,自动调用缺省或无参构造函数
	if ( poCN == NULL )				//判断动态内存申请是否成功。
	{
		exit(0);					//内存申请不成功,则退出
	}
	delete [ ]poCN;					//释放对象数组,去掉[ ]数组将释放不完全
	poCN = NULL;			//让poCN指针指向NULL。否则,它依然指向原动态空间

	poCN = new CNode( 5 );
	if ( poCN == NULL )				//判断动态内存申请是否成功
	{
		exit(0);
	}
	delete poCN;					//释放单个对象
	poCN = NULL;

	int *p1, *p2,(*p3)[3];			//p1,p2一般整型指针,p3行指针
	p1 = new int;				//new一个整数
	p2 = new int (200);			//new一个整数,其初值为200.
	p3 = new int[2][3];	//new一个关于一维数组的数组(该一维数组具有3个整型元素)
	delete p1;					//释放一个int数据
	delete p2;					//释放一个int数据
	delete [ ]p3;					//释放一个关于一维数组的数组
	p1 = NULL;
	p2 = NULL;
	p3 = NULL;
	//......

	//oCP1, oCP2为在堆栈(注意不是堆)中分配的对象,无须delete,系统自动释放。
	CNode oCP1( 1 );		//生成一般对象,就是对象变量,其值可以改变
	const CNode oCP2( 3);		//生成一个常对象。对象常量。其值不可改变
}

重载

//重载CNodeArray的new运算符
void *CNodeArray::operator new(size_t size)
{
	cout << "调用CNodeArray自定义的new创建对象.\n";
	return malloc(size);
}

//重载CNodeArray的delete运算符
void CNodeArray::operator delete(void *p)
{
	cout << "调用CNodeArray自定义的delete销毁对象.\n";
	free(p);
}
posted @ 2022-06-13 09:33  sz[sz]  阅读(113)  评论(0)    收藏  举报