C++堆栈变量和内存泄漏

栈时存在于某作用域的一块内存空间,当调用函数时,函数本身即会形成一个stack用来放置它接收的参数和返回地址。堆是操作系统提供的一块全局存储空间,程序可以动态分配从中获得若干区块。 

至于堆变量,操作系统虚拟存储器中为每个进程虚拟出独立的线性地址空间,堆即在其中。堆中的空间可以在程序运行过程中动态地分配和释放,通过返回指向堆中分配空间的指针堆该块区域进行操作。对于堆得操作常常会出现内存泄漏的情况,举个简单的例子,在调用函数中通过指针p指向堆中动态分配的某块区域,这里的指针变量是局部变量,函数结束时p指针自动被销毁。但是由于堆中分配的数据是通过p指针来索引的,这时这块堆区将不能再被利用,从而产生内存泄漏。

正因对于堆得操作存在许多问题,所以我们会把更多精力放在堆的操作上。在C++中,动态分配内存的操作是通过newdelete完成,使用new来动态分配变量主要有两大类,分配基本数据类型和分配类变量,分配基本数据类型在这里就不再详述,主要讨论分配类变量的情况。类变量又可以简单分成类似于complex类那样不包含指针的类和类似于string那样包含指针的类。

new语句动态分配类变量主要完成两项工作:分配内存空间(大致与malloc相同),调用被分配类的构造函数,简而言之就是先分配内存空间再对该块区域进行初始化。在动态分配类时,只为类中所包含的变量分配空间。

delete语句的顺序正好和new相反,不仅仅是因为功能上二者时相逆。delete主要完成的功能是:先调用类的析构函数,再回收分配的内存。

我们以分配string类变量为例。当通过new分配一个string类变量(String* p = new String();)时,定义在栈上的指针变量p指向在堆中分配的string类中唯一的变量m_data,而m_data也是指针类型的变量,m_data指向实际字符串。所以其实我们在创建新的string类变量时其实并不是直接分配了字符串的空间,而仅仅是为指向实际字符串的指针在堆上分配了空间。当堆中存在string类中的指针后,再通过m_data指针分配字符串空间(构造函数过程)。当我们通过delete语句销毁类变量时,之所以需要先执行string类中的析构函数(这里的析构函数时自己定义的,不是默认的析构函数,内含语句delete m_data;),是因为如果我们先把p所指向的堆空间释放了,则我们无法找到m_data指针变量,就没有办法去销毁m_data指针所指向的在堆上分配的字符串,也就造成了内存泄漏。所以这里的执行顺序时先释放m_data指向的字符串空间,再释放m_data指针变量的空间。

有了上面一段的知识铺垫,在解释array delete问题时就简单多了。考虑我们在分配时用了如下语句:String* p = new String[3]。这时delete语句一定要写成delete []p才行,否则会造成内存泄漏。与通常人们理解的内存泄漏小有差别的时,p所指向的堆上分配的三个m_data指针确实被释放了,但是如果仅仅写成delete p,则只会执行一次析构函数,也就是仅仅把第一个m_data指针所指向的字符串释放了,但是第二第三个m_data指针指向的字符串却产生了内存泄漏。

 

 

Advertisements