C/C++实战之内存管理
关键字:
C++,内存分配,系统
贴文时间 2001-11-11 21:23:47
原作 Paul_Ni
欢迎大家来到这片大多数程序员都心有余悸的雷区。本世纪伟大的比尔·盖茨曾经失言:
640K ought to be enough for everybody -- Bill Gates 1981
相信程序员们都经常要编写一些关于内存分配和使用的程序,而且都有过那种生不如死的感觉(当然我是指那种调试程序的感觉了,可能夸张了些!)
常见的内存分配和使用错误
1) 内存的申请和分配并没有成功,但程序员却使用了它。一些新手经常会犯这种错误,他们并不会留意到内存没有分配成功。判断指针的值是否为NULL可以有效地避免这种错误。
2) 内存的分配已经成功,但是却没有进行初始化就直接使用它了。首先是观念上的问题,很多人都没有在使用指针前要初始化这样的习惯,然而这个习惯却是很重要的,希望大家一定要强迫自己养成。第二就是主观地认为自己申请的内存的缺省值为0,这样想是没有什么道理的,内存分配后的值是不确定的。
3) 上面的两种工作都已经做好了(已经成功申请并初始化完成),但是操作时却越界了。
4) 申请了内存,使用完了却忘记了释放,导致内存泄露。这样的错误可以形容为一个恶性的肿瘤,它不会马上要你的命,但是它会慢慢地吞噬你的系统资源,直到你的程序彻底完蛋。
5) 你很小心地释放了内存,但是却又使用了它。由于程序很复杂或者调用顺序出错,这样可能导致出现上面的错误。
指针---一把伟大的双刃剑
我真的非常佩服发明指针的人,他简直太伟大了。能使用如此简洁地方法将复杂的内存结构描述的如此清楚,这本身就是一种伟大的成就。但是,指针之于程序员如同武器之于士兵,用好了可以威力无比,用不好则害人害己。
我先说说指针和数组的区别。数组名对应着一块内存,它的地址、容量在其生命周期中是不可变的,只有数组内容是可变的。指针可随时指向任何类型的内存,它的特点就是“变”。指针远比数组灵活,但也更危险。
数组名是不能直接进行赋值和比较的。如果你向要将数组a赋值给数组b,不能直接用赋值语句b = a ,这样会令编译器产生错误的。必须使用标准的库函数strcpy来进行赋值。相同地,要比较a和b的内容是否相同,不能使用普通的逻辑判断if(b==a),也要应用库函数strcmp来判断。
//数组……
char a[] = “hello”;
char b[100];
strcpy(b, a); // b = a is wrong
if (strcmp(b, a) == 0) //if (b == a) is wrong
cout<<b<<endl;
//指针……
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p, a);
if (strcmp(p, a) == 0)
cout<<p<<endl;
free(p);
在计算内存容量的时候有一点是必须要指出的,那就是sizeof计算数组是计算它的实际的内存容量,而计算指针时则永远都是4个字节。C++是永远没有办法直到指针所指的内存容量,除非在申请时记住它。
free和delete如何对付指针?
程序员都知道它们是用来释放申请的内存的,但是却很少有人注意到指针本身并没有发生什么变化。各位可以在VC中使用单步跟踪一下,你们会惊奇地发现当指针p被调用了free后它的地址值并没有改变,只是该地址对应的内存中原来有意义的值变成了垃圾,“p”却还是指向的这块内存。记住,一定要第一时间将p的值设为NULL,否则会让别人以为p是一个有意义的指针而误使用它(当别人使用该指针时会判断指针的值是否为NULL,如果不为NULL就会以为它有意义)。
char *p = (char *)malloc(100);
strcpy(p, “hello”);
free(p); // the address of “p” is not changed.
….
if (NULL != p) //it will return TRUE
strcpy(p, “world”); //Wrong!!!
下面提两点,让大家可以防止上面的情况出现:
1) 指针声明后要马上初始化。因为指针出现的缺省值是随机的,所以一定要赋值为NULL,然后再使用。
2) 调用了free和delete后一定要将指针赋值为NULL。原因上面已经提过了,就不再赘述了。
本文首先分析了使用内存会出现的常见错误。然后论述了内存使用过程中最为关键的一环 — 指针的一些平时不为人注意的用法和技巧。这些都是我平时在做工程项目中积累下的经验,希望能对大家(特别是那些还在内存的苦海中挣扎的苦难弟兄们)会有所帮助。有什么经验和问题需要交流的,请MAIL我。paulni@citiz.net
对该文的评论
sunny2001 ( 2001-11-13 16:22:46 )
很好,抛砖引玉,谢谢各位大侠!
Wind_LQ ( 2001-11-12 19:58:34 )
建议C初学者仔细看看,在学到一些基本常识的同时也顺便提高自己的除虫能力。
wildbaby ( 2001-11-12 17:26:17 )
无赖骗点。我还以为什么高手写的呢!!!这些基础教小学生的吧???
andy_show ( 2001-11-12 16:23:22 )
相信有许多文章提到过智能指针的用法,STL的auto_ptr是最好的例子,通过一个Template类实现大部分用new分配的内存的自动释放。另外一个办法是通过实现一个Reference Wrapper的Template类来实现基于引用计数的资源的自动释放,不过开销要比auto_ptr大,但是好处是可以实现对不同类型的系统资源的自动回收(如Windows系统中的内存,GDI,文件等Handle)。关于如何实现这样的类可以参考一下VC中_bstr_t的类的实现,虽然这个类只是局限于对于COM的BSTR类型的封装,不过其中的思想完全可以应用到像Handle那样的类型中。另为一个比较好的例子是_com_ptr_t的类,应该可以给各位不少启发。其实只要善用C++的特性,完全可以避免内存泄漏。不过,类似数组越界这样的问题还是无法避免。
thunder_yu ( 2001-11-12 14:38:36 )
呵呵,用了<<就叫c++了?
叫C实战之内存管理更好一些。
TopCat ( 2001-11-12 12:29:01 )
指出你文章中的几点小错误:
1. 内存分配失败返回NULL只有当你使用malloc函数家族时才正确,如果使用new,现在标准C++的做法是抛出一个std::bad_alloc类型的异常。
2. strcpy()函数只对字符指针才有效,因为它的结束条件是判断(*source)+x == '/0',如果把它用在其它类型的指针中,哼哼……,其实应该用memcpy()。strcmp也用相同的问题。
3. C++是不推荐使用malloc/free家族的,因为他们无法呼叫类的constructor和destructor,但你的代码中从来只出现了malloc/free,这起码让你的题目有一半名不副实。
4. 其实C++中的指针使用方法和注意事项与C中的区别还是非常大的。C++中更多的是考虑多型以及ctor/dtor的问题,你这篇文章可以说完全展现的是C的指针问题。(其实如上所说的数组问题,C++中可以使用vector和string最大限度的避免指针)。
我的评价:
在C++中,我从来都不这样做。而是:
#define SAFE_DELETE(p) (delete (p),(p)=NULL)
#if _DEBUG
#if defined(new) && defined(MYNEW)
#undef new
#endif
void * _cdecl operator new (size_t nSize,LPCSTR pFileName,DWORD dwLine)
{
1。使用其他内存分配函数分配内存
2。做一些操作,记录内存分配情况和分配次数
3。记录文件位置
4。小技巧:
_onexit();
怎么用?查查MSDN,或问问其他人
}
inline void * _cdecl operator new (size_t nSize)
{
同上,但是没有文件位置
}
inline void _cdecl operator delete (void * pMem,LPCSTR pFileName,DWORD dwLine)
{
1。使用其他内存释放函数
2。根据记录的内存分配信息来初步确定内存的合法性、越界等并输出错误信息
3。VC中的小技巧(其他的编译器就不知道了):
char buff[1024];
sprintf(buff,"%s(%d) : 出错信息/n",pFileName,dwLine,...);
OutputDebugString(buff);
这样,可以让VC直接跳到出错代码处,当然,仅限于调试运行(F5)并且MFC Trace中设置了输出调试信息。
}
inline void _cdecl operator delete (void * pMem)
{
1和2同上。
3。输出分配这块内存是第几次分配的,然后利用 VC 的条件断点跟踪出出错代码
}
#ifdef MYNEW
#define new MYNEW
#else
#define MYNEW new(__FILE__,__LINE__)
#endif
#endif //end _DEBUG
通过以上步骤,基本可以在调试版本杜绝内存使用错误