内存管理小结

本文聚焦C++内存管理,介绍了程序运行中静态、栈、堆三种内存类型,重点讲解堆内存使用,对比malloc/free和new/delete,强调不能随意搭配。还阐述了内存泄漏的多种情况,最后介绍了ARX对象构造与释放规则,如加入数据库对象的释放方式等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

内存管理小结

 


版本:0.1
作者:Soundboy
日期:2004-07-09
修改:
备注:本文介绍了C++语言开发中关于内存的相关知识,和Arx开发中的一些特殊注意事项。

内存基本知识

程序运行过程中使用的内存有主要有以下几种

静态内存

这部分内存在程序运行的整个过程中是始终归这个程序所有的,例如全局数据、static变量等等。

栈内存

定义的局部变量使用的是栈内存,这部分内存归操作系统管理。程序执行的时候动态分配,一旦这个变量超出作用域则被释放。

堆内存

以上两中内存都是系统管理的,一般来说程序员甚至感觉不到他们的存在(如果不主动思考)。但是堆内存是由程序代码来动态生成,同时又是由程序负责释放的。所以这中类型的内存功能最为强大,使用最为灵活,也最容易出问题。这部分也是本文介绍的重点。

堆内存的使用

在C语言中,使用malloc()/free()函数来使用堆内存。在C++语言中提供了两个操作符new/delete ,较之malloc/free,不但可以生成释放内存空间,而且还可以执行构造和析构函数。

malloc/free 和 new/delete

前面说过,malloc/free是两个函数,因此是不受编译器控制的。而new/delete是两个可以受编译器控制的操作符。malloc()函数执行的时候需要传入一个长度作为参数从而确定究竟需要分配多少内存,同时返回的指针也是没有类型的,往往需要强制转换为某种类型,例如:

char* p = (char*)malloc( sizeof(char) * 4);

在使用malloc分配内存的时候往往使用sizeof,这样可以为特定类型分配刚好是这种类型整数倍的空间,当然也可以直接用一个常量数字,但是这样往往不方便使用。

记住每次使用完了malloc之后要用free()函数来释放这个空间

free(p) //和上面的malloc成对使用

而new/delete,因为是操作符,所以编译器知道应该分配多长的空间和应该返回什么样的指针。更重要的,执行new的时候,这种数据类型的构造函数也被执行,而delete这个对象的时候,他的析构函数被执行。因此可以用如下伪代码来描述new和delete:

p = new Type; 过程: if ( p = malloc(sizeof(Type)) ) { p->Constructor() } else { p = NULL; } retur p; delete p; 过程: if ( p->~Destructor()) { free(p); }

这在面向对象开发的时候是非常方便的。

malloc/free 和 new /delete 可以搭配使用吗

一般来说,用malloc()分配的空间用free()来释放,用new分配的空间用delete来释放。如果用free()来释放new出的空间,则没有执行析构函数,有可能会出问题。而如果用delete来释放malloc()出的空间。也有可能会因为无法执行析构函数而出错,例如:

 CString *pStr = (CString*)malloc(sizeof(CString)); delete pStr; //因为构造函数没有执行,所以无法执行析构函数,这里会导致程序崩溃

对于类似int、char等基本类型,虽然互换使用不会出错,但是阅读上也会感觉概念混乱。

内存泄漏

内存泄漏属于资源泄漏的一种,其他的还有句柄未释放,连接未断开等等。所谓泄漏就是这个资源已经不使用了,而操作系统确认为它是被使用的。常见的内存泄漏有如下几种情况:

只分配,不释放

如果调用了malloc/new 而始终没有调用free/delete,就导致了这块内存始终被操作系统认为是使用的。这种情况是最基本的。

多分配,少释放

例子1

使用new操作符可以一次分配多个对象的空间,释放的时候必须都释放。例如下面这段代码中一次构造了5个char形对象(我们可以把char数据类型也看作是一种简洁的对象),而用delete p 只能释放一个。必须使用delete[]来释放。

char *p  = new char[5]; delete p; //错误 detete[] p; //正确
例子2

如果在一个循环中分配,那么必须同时在循环中释放或者用循环来释放,这也充分体现了new/delete必须“配对”使用的原则。例如下面的代码,for()循环中分配的空间,一共分配了5次,必须对每次都释放:

 char * p = NULL; for ( i=0 ; i<5 ; i++ ) { p = new char; .....; delete p; //正确 } delete p; //错误 

 或者在循环中把这些指针存放到一个数组中最终集中释放。

执行分支释放遗漏:

如果在执行分支外部分配的空间而在分支内部释放,必须对于所有的分支都要执行释放,下面的程序,函数执行失败的时候释放了内存,而执行成功的时候没有释放,属于内存泄漏。

class AObj; AObj *pA = new AObj; if ( !pA->Exce() ) { delete pA; ... } //仅仅在分支内部释放,而执行其他分支的时候会造成泄漏。 else { ... //这里也应该执行释放 }

“野空间”

指未被释放而且已经没有指针指向这段空间的内存,是一种低级的错误。

char* GetAString() { char *pStr = new char[5]; //这里申请了内存 pStr = "abcd"; //把pStr指向了一个常量地址,如果跟踪观察pStr的值,发现变化了 return pStr; }

上面这种情况是非常恐怖的,因为申请的内存即使想释放都不成了,已经没有指向它的指针了。

调用分配函数未释放

有一些函数,在内部分配的空间用一个指针返回,释放的工作则留给了这个函数的调用者。例如:

 CreateARect(CMyRect*& pRect);

假设CMyRect是我们自己定义的一个关于矩形的类,这个函数内部构造了一个 CMyRect类的堆对象,这个对象由调用者使用并负责销毁。

原则上来说,这种设计应该避免。如果这么做了,应该有个与之对应的DeleteARect()之类的函数。但是因为ARX特殊的数据库机理,在ARX开发中是一种比较常见的情况。这就要求我们必须高度注意一个有新构造的函数的使用。

常量指针不需要释放

由于常量数据是存放在静态栈内存中的,因此不需要程序专门释放。上面讲的,返回一个字符串,如果是常量类型,那么是不需要释放的。

ARX对象的构造与释放

在针对AutoCAD 二次开发的Object ARX 开发中,对象的使用有着自己特定的一套机理,必须和C++对象的内存管理配合使用。并且务必做到概念清晰,才不至于出错。

ARX对象

ARX中的对象本质上也是一个C++的类对象,但是ARX自己有一套类库,有自己的运行时库。因此只有符合一定条件(从几个特定的ARX类派生)特定的类对象才是ARX对象。

数据库

对于一个.dwg文件,我们称之为一个数据库,这个数据库中有多个ARX对象,有一部分时必须存在的有些则是可选的。这些对象之间有特定的关系。

加入数据库

在ARX中,加入数据库是指把一个对象按照ARX特定关系和数据库中原有的对象关联起来。

ARX管理对象的几个规则

加入数据库的必须是一个堆对象

就是说,对于这个对象的类,例如CMyRect,必须用如下的方法得到的对象才可以加入到数据库。

CMyRect *pRect = new CMyRect; 
加入数据库的对象不能直接释放

按照C++的内存管理知识,对于这样的指针必须有delete pRect;这样的语句。ARX是C++的子集,当然也概莫能外。但是ARX有自己的一套机制来处理这件事情,使得一旦这个指针所指的对象(一块在堆上的内存)被加入数据库中后,我们不能再使用delete来释放这个对象,而要使用ARX自己的关闭函数来释放。

pRect->close(); 

执行这个操作后,实际上堆内存的释放工作是交给AutoCAD来完成了。

这样,对于ARX开发,没有加入数据库中的对象使用C++的delete来释放,加入数据库中的对象使用close()来关闭。因此要求开发人员对于什么时候应该delete,什么时候应该close()时刻保持清醒。简而言之,当且仅当加入数据库的对象必须调用close()

ARX库函数返回指针的释放

ARX中有许多函数返回一个指针,例如:

getDescription(char*& desc) const; //返回的desc需要释放 getName(char*& name) const; //返回的name 需要释放

查看这些函数的在线文档,都有类似“The caller of this function is responsible for releasing the memory used by the...”这样的语句,提示调用这个函数的程序释放使用的指针。我们应该在调用一个函数的时候仔细阅读在线文档。在自己写的需要调用者释放的函数的注释里面明确要求调用者释放。

Result Buffers指针要用特定的函数释放

ARX的Result Buffers是一个特殊的链表结构体,有许多函数,例如acutBuildList()等,可以生成一个此种类型的堆对象,都需要用如下方法释放:

 acutRelRb(result); //result是一个 Result Buffer类型的指针

AR开发还有其他需要注意内存管理的的地方,需要在具体开发时按照在线文档适当处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值