《C++内存管理详解1121.docx》由会员分享,可在线阅读,更多相关《C++内存管理详解1121.docx(26页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C+内存管理详解程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本文的内容比一般教科书的要深入得多,读者需细心阅读,做到真正地通晓内存管理。 1、内存分配方式内存分分配方式式有三种种:(1)从静静态存储储区域分分配。内内存在程程序编译译的时候候就已经经分配好好,这块块内存在在程序的的整个运运行期间间都存在在。例如如全局变变量,sstattic变变量。(2)在在栈上创创建。在在执行函函数时,函函数内局局部变量量的存储储单元都都可以在在栈上创创建,函函数执行行结束时时这些存存储单元元自动被被释放。栈栈内存分分配运算算内置于于
2、处理器器的指令令集中,效效率很高高,但是是分配的的内存容容量有限限。(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。2、常见的的内存错错误及其其对策发发生内存存错误是是件非常常麻烦的的事情。编编译器不不能自动动发现这这些错误误,通常常是在程程序运行行时才能能捕捉到到。而这这些错误误大多没没有明显显的症状状,时隐隐时现,增增加了改改错的难难度。有有时用户户怒气冲冲冲地把把你找来来,程序序却没有有发生任任何问题题,你一一走,错错误又发发作了。
3、 常见的内存错误及其对策如下:* 内内存分配配未成功功,却使使用了它它。编程新新手常犯犯这种错错误,因因为他们们没有意意识到内内存分配配会不成成功。常常用解决决办法是是,在使使用内存存之前检检查指针针是否为为NULLL。如如果指针针p是函数数的参数数,那么么在函数数的入口口处用aasseert(p!=NULLL)进进行检查。如如果是用用malllocc或neww来申请请内存,应应该用iif(pp=NNULLL) 或或if(p!=NULLL)进进行防错错处理。* 内内存分配配虽然成成功,但但是尚未未初始化化就引用用它。犯这种种错误主主要有两两个起因因:一是是没有初初始化的的观念;二是误误以为内内
4、存的缺缺省初值值全为零零,导致致引用初初值错误误(例如如数组)。内内存的缺缺省初值值究竟是是什么并并没有统统一的标标准,尽尽管有些些时候为为零值,我我们宁可可信其无无不可信信其有。所所以无论论用何种种方式创创建数组组,都别别忘了赋赋初值,即即便是赋赋零值也也不可省省略,不不要嫌麻麻烦。* 内内存分配配成功并并且已经经初始化化,但操操作越过过了内存存的边界界。例如在在使用数数组时经经常发生生下标“多1”或者者“少1”的操操作。特特别是在在forr循环语语句中,循循环次数数很容易易搞错,导导致数组组操作越越界。* 忘忘记了释释放内存存,造成成内存泄泄露。含有这这种错误误的函数数每被调调用一次次就丢
5、失失一块内内存。刚刚开始时时系统的的内存充充足,你你看不到到错误。终终有一次次程序突突然死掉掉,系统统出现提提示:内内存耗尽尽。动态内内存的申申请与释释放必须须配对,程程序中mmallloc与与freee的使使用次数数一定要要相同,否否则肯定定有错误误(neew/ddeleete同同理)。* 释释放了内内存却继继续使用用它。有有三种情情况:(1)程程序中的的对象调调用关系系过于复复杂,实实在难以以搞清楚楚某个对对象究竟竟是否已已经释放放了内存存,此时时应该重重新设计计数据结结构,从从根本上上解决对对象管理理的混乱乱局面。(2)函函数的rretuurn语语句写错错了,注注意不要要返回指指向“栈内
6、存存”的“指针”或者“引用”,因为为该内存存在函数数体结束束时被自自动销毁毁。(3)使使用frree或或delletee释放了了内存后后,没有有将指针针设置为为NULLL。导导致产生生“野指针针”。【规则则1】用maallooc或neww申请内内存之后后,应该该立即检检查指针针值是否否为NUULL。防防止使用用指针值值为NUULL的的内存。【规则则2】不要要忘记为为数组和和动态内内存赋初初值。防防止将未未被初始始化的内内存作为为右值使使用。【规则则3】避免免数组或或指针的的下标越越界,特特别要当当心发生生“多1”或者者“少1”操作作。【规则则4】动态态内存的的申请与与释放必必须配对对,防止止内
7、存泄泄漏。【规则则5】用frree或或delletee释放了了内存之之后,立立即将指指针设置置为NUULL,防防止产生生“野指针针”。3、指针与与数组的的对比C+/C程程序中,指指针和数数组在不不少地方方可以相相互替换换着用,让让人产生生一种错错觉,以以为两者者是等价价的。数组要要么在静静态存储储区被创创建(如如全局数数组),要要么在栈栈上被创创建。数数组名对对应着(而而不是指指向)一一块内存存,其地地址与容容量在生生命期内内保持不不变,只只有数组组的内容容可以改改变。指针可可以随时时指向任任意类型型的内存存块,它它的特征征是“可变”,所以以我们常常用指针针来操作作动态内内存。指指针远比比数组
8、灵灵活,但但也更危危险。下面以以字符串串为例比比较指针针与数组组的特性性。3.11 修改改内容示例33-1中中,字符符数组aa的容量量是6个字符符,其内内容为hhelllo。a的内容容可以改改变,如如a00= X。指针针p指向常常量字符符串“worrld”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p0= X有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。char a = “hello”;a0 = X;cout a endl;char *p = “world”; / 注意p指向常量字符串p0 = X; / 编译器不能发现
9、该错误cout p endl;示例3.1 修改数组和指针的内容 3.2 内内容复制制与比较较不能对对数组名名进行直直接复制制与比较较。示例例7-33-2中中,若想想把数组组a的内容容复制给给数组bb,不能能用语句句 b = aa ,否否则将产产生编译译错误。应应该用标标准库函函数sttrcppy进行行复制。同同理,比比较b和a的内容容是否相相同,不不能用iif(bb=aa) 来来判断,应应该用标标准库函函数sttrcmmp进行行比较。语句pp = a 并并不能把把a的内容容复制指指针p,而是是把a的地址址赋给了了p。要想想复制aa的内容容,可以以先用库库函数mmallloc为为p申请一一块容量
10、量为sttrleen(aa)+11个字符符的内存存,再用用strrcpyy进行字字符串复复制。同同理,语语句iff(p=a) 比较较的不是是内容而而是地址址,应该该用库函函数sttrcmmp来比比较。/ 数组组chaar aa = helllo;chhar b110;strrcpyy(b, a); / 不不能用 b = a;if(strrcmpp(b, a) = 0) / 不能能用 iif (b = aa)/ 指针针intt leen = sttrleen(aa);ccharr *pp = (chhar *)mmallloc(sizzeoff(chhar)*(llen+1);sttrcppy(
11、pp,a); / 不不要用 p = a;if(strrcmpp(p, a) = 0) / 不要要用 iif (p = aa)示示例3.2 数数组和指指针的内内容复制制与比较较 3.3 计算内内存容量量用运算算符siizeoof可以以计算出出数组的的容量(字字节数)。示例7-3-3(a)中,sizeof(a)的值是12(注意别忘了)。指针p指向a,但是 sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。 C+/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。 注意当数组作为函数的参数进行传
12、递时,该数组自动退化为同类型的指针。示例7-3-3(b)中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。char a = heelloo woorldd;ccharr *pp = a;ccoutt sizzeoff(a) enndl; / 122字节coout ssizeeof(p) enddl; / 4字节节示示例3.3(a) 计算数数组和指指针的内内存容量量 voidd Fuunc(chaar aa1000)coout ssizeeof(a) enddl; / 4字节节而不是是1000字节示示例3.3(b) 数组退退化为指指针4、指针参参数是如如何传递递
13、内存的的? 如果函函数的参参数是一一个指针针,不要要指望用用该指针针去申请请动态内内存。示示例7-4-11中,Teest函函数的语语句GeetMeemorry(sstr, 2000)并并没有使使strr获得期期望的内内存,sstr依依旧是NNULLL,为什什么?voiid GGetMMemoory(chaar *p, intt nuum)p = (chaar *)maallooc(ssizeeof(chaar) * nnum);voiid TTestt(vooid)chhar *sttr = NUULL;GettMemmoryy(sttr, 1000); / strr 仍然然为 NNULLLs
14、trrcpyy(sttr, heelloo); / 运行行错误示例44.1 试图用用指针参参数申请请动态内内存 毛病出在在函数GGetMMemoory中中。编译译器总是是要为函函数的每每个参数数制作临临时副本本,指针针参数pp的副本本是 _p,编编译器使使 _pp = p。如如果函数数体内的的程序修修改了_p的内内容,就就导致参参数p的内容容作相应应的修改改。这就就是指针针可以用用作输出出参数的的原因。在在本例中中,_pp申请了了新的内内存,只只是把 _p所所指的内内存地址址改变了了,但是是p丝毫未未变。所所以函数数GettMemmoryy并不能能输出任任何东西西。事实实上,每每执行一一次Ge
15、etMeemorry就会会泄露一一块内存存,因为为没有用用freee释放放内存。如果非非得要用用指针参参数去申申请内存存,那么么应该改改用“指向指指针的指指针”,见示示例4.2。voiid GGetMMemoory22(chhar *pp, iint numm)*pp = (chhar *)mmallloc(sizzeoff(chhar) * numm);voiid TTestt2(vvoidd)chhar *sttr = NUULL;GeetMeemorry2(&sttr, 1000); / 注意参参数是 &sttr,而而不是sstrstrrcpyy(sttr, heelloo);coout
16、 sstr enddl;freee(sstr);示例44.2用用指向指指针的指指针申请请动态内内存 由于“指向指指针的指指针”这个概概念不容容易理解解,我们们可以用用函数返返回值来来传递动动态内存存。这种种方法更更加简单单,见示示例4.3。char *GeetMeemorry3(intt nuum)chhar *p = (chaar *)maallooc(ssizeeof(chaar) * nnum);rretuurn p;voiid TTestt3(vvoidd)chhar *sttr = NUULL;sttr = GeetMeemorry3(1000);strrcpyy(sttr, hee
17、lloo);coout sstr enddl;freee(sstr);示例例4.33 用函函数返回回值来传传递动态态内存 用函数返返回值来来传递动动态内存存这种方方法虽然然好用,但但是常常常有人把把retturnn语句用用错了。这这里强调调不要用用retturnn语句返返回指向向“栈内存存”的指针针,因为为该内存存在函数数结束时时自动消消亡,见见示例44.4。chaar *GettStrringg(vooid)chhar p = heelloo woorldd;retturnn p; / 编译器器将提出出警告voiid TTestt4(vvoidd)chhar *sttr = NUULL;st
18、tr = GeetSttrinng(); / sstr 的内容容是垃圾圾coout sstr enddl;示例44.4 retturnn语句返返回指向向“栈内存存”的指针针 用调试试器逐步步跟踪TTestt4,发发现执行行strr = GettStrringg语句后后strr不再是是NULLL指针针,但是是strr的内容容不是“helllo worrld”而是垃垃圾。如如果把示示例4.4改写写成示例例4.55,会怎怎么样?char *GeetSttrinng2(voiid)chhar *p = helllo worrld;rretuurn p;voiid TTestt5(vvoidd)chha
19、r *sttr = NUULL;strr = GettStrringg2();ccoutt strr enndl;示示例4.5 rretuurn语语句返回回常量字字符串函数TTestt5运行行虽然不不会出错错,但是是函数GGetSStriing22的设计计概念却却是错误误的。因因为GeetSttrinng2内内的“helllo worrld”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。5、杜绝“野指针”“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判
20、断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种:(1)指指针变量量没有被被初始化化。任何何指针变变量刚被被创建时时不会自自动成为为NULLL指针针,它的的缺省值值是随机机的,它它会乱指指一气。所所以,指指针变量量在创建建的同时时应当被被初始化化,要么么将指针针设置为为NULLL,要要么让它它指向合合法的内内存。例例如char *p = NNULLL;chhar *sttr = (ccharr *) maallooc(1100); (2)指指针p被freee或者者delletee之后,没没有置为为NULLL,让让人误以以为p是个合合法的指指针。(3)指指针操作作
21、超越了了变量的的作用范范围。这这种情况况让人防防不胜防防,示例例程序如如下:classs Apuubliic:vooid Funnc(vvoidd) couut “FFuncc off cllasss A” Fuunc(); / p是“野指针针”函数TTestt在执行行语句pp-FFuncc()时时,对象象a已经消消失,而而p是指向向a的,所所以p就成了了“野指针针”。但奇奇怪的是是我运行行这个程程序时居居然没有有出错,这这可能与与编译器器有关。6、有了mmallloc/freee为什什么还要要neww/deelette?malllocc与freee是C+/C语语言的标标准库函函数,nnew/
22、delletee是C+的运算算符。它它们都可可用于申申请动态态内存和和释放内内存。对于非非内部数数据类型型的对象象而言,光光用maalocc/frree无无法满足足动态对对象的要要求。对对象在创创建的同同时要自自动执行行构造函函数,对对象在消消亡之前前要自动动执行析析构函数数。由于于malllocc/frree是是库函数数而不是是运算符符,不在在编译器器控制权权限之内内,不能能够把执执行构造造函数和和析构函函数的任任务强加加于maallooc/ffreee。因此CC+语语言需要要一个能能完成动动态内存存分配和和初始化化工作的的运算符符neww,以及及一个能能完成清清理与释释放内存存工作的的运算
23、符符delletee。注意意 neew/ddeleete不不是库函函数。我我们先看看一看mmallloc/freee和neww/deelette如何何实现对对象的动动态内存存管理,见见示例66。classs Obbjpuubliic :OObj(voiid) coout “Innitiialiizattionn” eendll; Objj(vooid) ccoutt “DDesttroyy” eendll; vvoidd Innitiialiize(voiid) coout “Innitiialiizattionn” eendll; vvoidd Deestrroy(voiid) coout
24、“Deestrroy” Iniitiaalizze(); / 初初始化/a-Desstrooy(); / 清清除工作作freee(aa); / 释放内内存voiid UUseNNewDDeleete(voiid)Objj *aa = neww Obbj; / 申请动动态内存存并且初初始化/deelette aa; / 清清除并且且释放内内存示示例6 用malllocc/frree和和neww/deelette如何何实现对对象的动动态内存存管理类Obbj的函函数Innitiialiize模模拟了构构造函数数的功能能,函数数Desstrooy模拟拟了析构构函数的的功能。函函数UsseMaalloo
25、cFrree中中,由于于 maallooc/ffreee不能执执行构造造函数与与析构函函数,必必须调用用成员函函数Innitiialiize和和Desstrooy来完完成初始始化与清清除工作作。函数数 UsseNeewDeelette则简简单得多多。所以我我们不要要企图用用malllocc/frree来来完成动动态对象象的内存存管理,应应该用nnew/delletee。由于于内部数数据类型型的“对象”没有构构造与析析构的过过程,对对它们而而言maallooc/ffreee和neww/deelette是等等价的。既然nnew/delletee的功能能完全覆覆盖了mmallloc/freee,为为
26、什么CC+不不把maallooc/ffreee淘汰出出局呢?这是因因为C+程序序经常要要调用CC函数,而而C程序只只能用mmallloc/freee管理理动态内内存。如如果用ffreee释放“neww创建的的动态对对象”,那么么该对象象因无法法执行析析构函数数而可能能导致程程序出错错。如果果用deelette释放放“malllocc申请的的动态内内存”,理论论上讲程程序不会会出错,但但是该程程序的可可读性很很差。所所以neew/ddeleete必必须配对对使用,malloc/free也一样。 7、内存耗耗尽怎么么办?如如果在申申请动态态内存时时找不到到足够大大的内存存块,mmallloc和和n
27、eww将返回回NULLL指针针,宣告告内存申申请失败败。通常常有三种种方式处处理“内存耗耗尽”问题。 (1)判断断指针是是否为NNULLL,如果果是则马马上用rretuurn语语句终止止本函数数。例如如:vooid Funnc(vvoidd)A *a = neew AA;iif(aa = NUULL)retturnn;(2)判断断指针是是否为NNULLL,如果果是则马马上用eexitt(1)终止整整个程序序的运行行。例如如:vooid Funnc(vvoidd)A *a = nnew A;if(a = NNULLL)ccoutt “MMemoory Exhhausstedd” eendll;e
28、xiit(11); (3)为为neww和malllocc设置异异常处理理函数。例例如Viisuaal CC+可可以用_sett_neew_hhandder函函数为nnew设设置用户户自己定定义的异异常处理理函数,也也可以让让malllocc享用与与neww相同的的异常处处理函数数。详细细内容请请参考CC+使使用手册册。上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。很多人人不忍心心用exxit(1),问问:“不编写写出错处处理程序序,让操操作系统统自己解解决行不不行?”不行。如如果发生生“内存耗耗尽”
29、这样的的事情,一一般说来来应用程程序已经经无药可可救。如如果不用用exiit(11) 把把坏程序序杀死,它它可能会会害死操操作系统统。道理理如同:如果不不把歹徒徒击毙,歹歹徒在老老死之前前会犯下下更多的的罪。有一个个很重要要的现象象要告诉诉大家。对对于322位以上上的应用用程序而而言,无无论怎样样使用mmallloc与与neww,几乎乎不可能能导致“内存耗耗尽”。我在在Winndowws 998下用用Vissuall C+编写写了测试试程序,见见示例77。这个个程序会会无休止止地运行行下去,根根本不会会终止。因因为322位操作作系统支支持“虚存”,内存存用完了了,自动动用硬盘盘空间顶顶替。我我
30、只听到到硬盘嘎嘎吱嘎吱吱地响,Window 98已经累得对键盘、鼠标毫无反应。我可以以得出这这么一个个结论:对于332位以以上的应应用程序序,“内存耗耗尽”错误处处理程序序毫无用用处。这这下可把把Uniix和Winndowws程序序员们乐乐坏了:反正错错误处理理程序不不起作用用,我就就不写了了,省了了很多麻麻烦。我不想想误导读读者,必必须强调调:不加加错误处处理将导导致程序序的质量量很差,千千万不可可因小失失大。vvoidd maain(voiid)flloatt *pp = NULLL;whiile(TRUUE)p = neew ffloaat1100000000;coout “eaat m
31、memoory” enddl;iff(p=NUULL)exiit(11);示例77试图耗耗尽操作作系统的的内存 8、maallooc/ffreee 的使使用要点点函数mmallloc的的原型如如下:void * mmallloc(sizze_tt siize); 用maallooc申请请一块长长度为llenggth的的整数类类型的内内存,程程序如下下:int *p = (iint *) malllocc(siizeoof(iint) * lenngthh); 我们应应当把注注意力集集中在两两个要素素上:“类型转转换”和“sizzeoff”。* mmallloc返回值值的类型型是vooid *,
32、所所以在调调用maallooc时要要显式地地进行类类型转换换,将vvoidd * 转换成成所需要要的指针针类型。* mmallloc函函数本身身并不识识别要申申请的内内存是什什么类型型,它只只关心内内存的总总字节数数。我们们通常记记不住iint, flloatt等数据据类型的的变量的的确切字字节数。例例如innt变量量在166位系统统下是22个字节节,在332位下下是4个字节节;而ffloaat变量量在166位系统统下是44个字节节,在332位下下也是44个字节节。最好好用以下下程序作作一次测测试:cout sizzeoff(chhar) enndl;couut ssizeeof(intt)
33、eendll;coout sizzeoff(unnsiggnedd innt) enddl;ccoutt siizeoof(llongg) eendll;coout sizzeoff(unnsiggnedd loong) enndl;couut ssizeeof(flooat) enndl;couut ssizeeof(douublee) eendll;coout sizzeoff(vooid *) enddl; 在maallooc的“()”中中使用ssizeeof运运算符是是良好的的风格,但但要当心心有时我我们会昏昏了头,写写出 pp = malllocc(siizeoof(pp)这这样的程
34、程序来。* 函函数frree的的原型如如下:void freee( voiid * meembllockk ); 为什么么freee 函函数不象象malllocc函数那那样复杂杂呢?这这是因为为指针pp的类型型以及它它所指的的内存的的容量事事先都是是知道的的,语句句freee(pp)能正正确地释释放内存存。如果果p是 NUULL指指针,那那么frree对对p无论操操作多少少次都不不会出问问题。如如果p不是NUULL指指针,那那么frree对对p连续操操作两次次就会导导致程序序运行错错误。9、neww/deelette 的的使用要要点运算符符neww使用起起来要比比函数mmallloc简简单得多
35、多,例如如:int *p1 = (intt *)malllocc(siizeoof(iint) * lenngthh);iint *p22 = neww inntllenggth; 这是因因为neew内置置了siizeoof、类类型转换换和类型型安全检检查功能能。对于于非内部部数据类类型的对对象而言言,neew在创创建动态态对象的的同时完完成了初初始化工工作。如如果对象象有多个个构造函函数,那那么neew的语语句也可可以有多多种形式式。例如如classs Obbjpuubliic :Obbj(vvoidd); / 无参数数的构造造函数Obbj(iint x); / 带一个个参数的的构造函函数v
36、oiid TTestt(vooid)Obbj *a = neew OObj;Obbj *b = neew OObj(1); / 初值值为1deelette aa;ddeleete b;如果用用neww创建对对象数组组,那么么只能使使用对象象的无参参数构造造函数。例例如Obj *objjectts = neew OObj1000; / 创建1000个动动态对象象不不能写成成Objj *oobjeectss = neww Obbj1100(1);/ 创建建1000个动态态对象的的同时赋赋初值11在在用deelette释放放对象数数组时,留留意不要要丢了符符号。例如如delette obbjecct
37、s; / 正确确的用法法delletee obbjeccts; / 错误误的用法法 后者相相当于ddeleete objjectts00,漏漏掉了另另外999个对象象。10、一些些心得体体会我认认识不少少技术不不错的CC+/C程序序员,很很少有人人能拍拍拍胸脯说说通晓指指针与内内存管理理(包括括我自己己)。我我最初学学习C语言时时特别怕怕指针,导导致我开开发第一一个应用用软件(约约1万行C代码)时时没有使使用一个个指针,全全用数组组来顶替替指针,实实在蠢笨笨得过分分。躲避避指针不不是办法法,后来来我改写写了这个个软件,代代码量缩缩小到原原先的一一半。我的的经验教教训是:(1)越是是怕指针针,就越越要使用用指针。不不会正确确使用指指针,肯肯定算不不上是合合格的程程序员。(2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。