这篇文章简单讨论下c++中的内存泄漏问题。在本例中需要用到第一篇中关于内存泄漏检测的头文件,用来辅助检测程序中的内存泄漏情况。
1.只分配不释放
程序1:
#include <stdio.h>
#include <string.h>
#include "common.h"
int main()
{
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF|_CRTDBG_ALLOC_MEM_DF);
int *p = (int*)malloc(sizeof(int));
int *q = new int;
return 0;
}
上面程序中分别使用malloc和new分配了两个int型内存。但没有对动态分配的内存进行释放。程序运行结束,在vs2008输出窗口输出如下信息
2.分支引起的内存未释放
程序2:
#include <stdio.h>
#include <string.h>
#include "common.h"
void func(int flag)
{
int *p = new int;
if(flag)
{
return;
}
delete p;
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF|_CRTDBG_ALLOC_MEM_DF);
func(1);
return 0;
}
上面的程序中,在func中我们动态分配的内存空间,在程序最后使用delete p;释放了内存。但上面程序运行结束后会在vs2008输出窗口提示我们func中的int *p = new int;分配的空间没有释放。简单分析下是由于程序中判断了参数flag的值,如果为真,程序就直接返回。后面的释放代码就无法执行了。其实上面的func代码比较简单,所以还是比较容易能找到原因。但在实际开发中功能比较复杂,判断复杂,有时并不太容易发现内存泄漏。并且有时内存泄漏的情况只有在特定条件下才会发生,如上面的程序。如果func传的参数值一直都是0 ,那么内存泄漏永远都不会发生。
3.异常情况引起的内存泄漏
程序3:
#include <stdio.h>
#include <string.h>
#include <iostream>
#include "common.h"
void func(int iNum)
{
int iVal = 100;
int *p = new int;
if(iNum==0)
{
throw std::exception("除数为0");
}
*p = iVal/iNum;
delete p;
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF|_CRTDBG_ALLOC_MEM_DF);
try
{
func(0);
}
catch(...)
{
}
return 0;
}
上面的程序执行完成后会提示第8行分配的内存没有释放。这是因为我们在func函数判断当iNum==0时,抛出一个异常,这时程序会返回到main函数中进行处理,func最后两句就不会再被执行,从而导致内存泄漏。上面的程序我们完全可以在判断iNum==0的时候执行释放语句。程序这么只是简单的模拟,因为在实际开发中,我们不可能判断出所有的异常情况。对于比较有可能发生异常的函数内,我们尽量不要动态分配内存空间;在必须要动态分配内存的情况下,如果处理的是字符串,那么使用stl string或第三方开源c++字符串处理类的对象来实现;如果是为对象分配动态空间,使用智能指针来管理对象指针,因为C++保证在发生异常时,局部对象的析构函数一定会被调用。
4.继承引起的内存泄漏
在我做的保密检查工具中需要对ntfs及fat32文件系统中被删除的文件进行恢复。程序大致结构如下:
程序4:
class CFileSystem
{
};
class CFat32FileSystem:public CFileSystem
{
};
class CNtfsFileSystem:public CFileSystem
{
public:
CNtfsFileSystem()
{
m_bitmap = new(xxxx);
}
~CNtfsFileSystem()
{
delete m_bitmap;
}
private:
char *m_bitmap;
};
ntfs中的m_bitmap是用于记录分区中哪些扇区已被使用,哪些扇区是空闲的。该功能的使用大致如下:
CFileSystem *fileSystem = new CNtfsFileSystem;
delete fileSystem;
父类指针对象指向子类,这样无论子类是ntfs或者fat32。操作接口都是统一的。使用完后删除父类指针所指对象。后面在测试过中发现使用该功能,程序的内存会一直不停增长;后面分析发现虽然调用了delete fileSystem;但并没有调用CNtfsFileSystem的析构函数。这是由于我们没有把基类CFileSystem的析构函数定义成函数,这样执行delete语句只调用了父类的析构函数,没有调用子类CNtfsSystem的析构函数;造成了子类m_bitmap分配的空间没有释放。所以在编写一个类的时候,除非我们确定该类不会被继承,否则最好将该类的析构函数定义成虚函数。
5.指针类型转换引起的内存泄漏
#include <stdio.h>
#include <string.h>
#include "common.h"
using namespace std;
class A
{
public:
A(){p = new char[1024];}
~A(){delete p;}
private:
char *p ;
};
int main()
{
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF|_CRTDBG_ALLOC_MEM_DF);
void *p = new A;
delete p;
return 0;
}
上面的程序定义了一个类A,在A的无参构造函数中给成员变量p动态分配了1024字节。在main函数中使用new创建一个A的对象,p指向新创建的对象。上面程序运行完成后却提示我们程序中有内存泄露。如下图
看下上面的main函数中,将p定义成了void*型,这样p可以指向任意的指针类型;执行delete p语句首先删除p所分配的空间,然后p是void *型,所以不会调用p所指向的真实类型的析构函数。上面的程序释放了分配类A的对象所占用空间,但是对于类A对象的成员在构造时动态分配的内存没有释放。从而产生内存泄漏。