C++内存管理详解
? 程式員們經(jīng)常編寫內(nèi)存管理程式,往往提心吊膽。假如不想觸雷,唯一的解決辦法就是發(fā)現(xiàn)任何潛伏的地雷并且排除他們,躲是躲不了的。本文的內(nèi)容比一般教科書的要深入得多,讀者需細(xì)心閱讀,做到真正地通曉內(nèi)存管理。?
??1、內(nèi)存分配方式?
??內(nèi)存分配方式有三種:?
??(1)從靜態(tài)存儲(chǔ)區(qū)域分配。內(nèi)存在程式編譯的時(shí)候就已分配好,這塊內(nèi)存在程式的整個(gè)運(yùn)行期間都存在。例如全局變量,static變量。?
??(2)在棧上創(chuàng)建。在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都能夠在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。?
??(3)?從堆上分配,亦稱動(dòng)態(tài)內(nèi)存分配。程式在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意多少的內(nèi)存,程式員自己負(fù)責(zé)在何時(shí)用free或delete釋放內(nèi)存。動(dòng)態(tài)內(nèi)存的生存期由我們決定,使用很靈活,但問題也最多。?
??2、常見的內(nèi)存錯(cuò)誤及其對(duì)策?
??發(fā)生內(nèi)存錯(cuò)誤是件很麻煩的事情。編譯器不能自動(dòng)發(fā)現(xiàn)這些錯(cuò)誤,通常是在程式運(yùn)行時(shí)才能捕獲到。而這些錯(cuò)誤大多沒有明顯的癥狀,時(shí)隱時(shí)現(xiàn),增加了改錯(cuò)的難度。有時(shí)用戶怒氣沖沖地把您找來(lái),程式卻沒有發(fā)生任何問題,您一走,錯(cuò)誤又發(fā)作了。?常見的內(nèi)存錯(cuò)誤及其對(duì)策如下:?
??*?內(nèi)存分配未成功,卻使用了它。?
??編程新手常犯這種錯(cuò)誤,因?yàn)樗麄儧]有意識(shí)到內(nèi)存分配會(huì)不成功。常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。假如指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(p!=NULL)進(jìn)行?
??檢查。假如是用malloc或new來(lái)申請(qǐng)內(nèi)存,應(yīng)該用if(p==NULL)?或if(p!=NULL)進(jìn)行防錯(cuò)處理。?
??*?內(nèi)存分配雖然成功,但是尚未初始化就引用它。?
??犯這種錯(cuò)誤主要有兩個(gè)起因:一是沒有初始化的觀念;二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯(cuò)誤(例如數(shù)組)。?內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn),盡管有些時(shí)候?yàn)榱阒?#xff0c;我們寧可信其無(wú)不可信其有。所以無(wú)論用何種方式創(chuàng)建數(shù)組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。?
??*?內(nèi)存分配成功并且已初始化,但操作越過了內(nèi)存的邊界。?
??例如在使用數(shù)組時(shí)經(jīng)常發(fā)生下標(biāo)“多1”或“少1”的操作。特別是在for循環(huán)語(yǔ)句中,循環(huán)次數(shù)很容易搞錯(cuò),導(dǎo)致數(shù)組操作越界。
*?忘記了釋放內(nèi)存,造成內(nèi)存泄露。?
??含有這種錯(cuò)誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存。剛開始時(shí)系統(tǒng)的內(nèi)存充足,您看不到錯(cuò)誤。終有一次程式突然死掉,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡。?
??動(dòng)態(tài)內(nèi)存的申請(qǐng)和釋放必須配對(duì),程式中malloc和free的使用次數(shù)一定要相同,否則肯定有錯(cuò)誤(new/delete同理)。?
??*?釋放了內(nèi)存卻繼續(xù)使用它。??
??有三種情況:?
??(1)程式中的對(duì)象調(diào)用關(guān)系過于復(fù)雜,實(shí)在難以搞清楚某個(gè)對(duì)象究竟是否已釋放了內(nèi)存,此時(shí)應(yīng)該重新設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),從根本上解決對(duì)象管理的混亂局面。?
??(2)函數(shù)的return語(yǔ)句寫錯(cuò)了,注意不要返回指向“棧內(nèi)存”的“指針”或“引用”,因?yàn)樵搩?nèi)存在函數(shù)體結(jié)束時(shí)被自動(dòng)銷毀。?
??(3)使用free或delete釋放了內(nèi)存后,沒有將指針配置為NULL。導(dǎo)致產(chǎn)生“野指針”。?
??【規(guī)則1】用malloc或new申請(qǐng)內(nèi)存之后,應(yīng)該立即檢查指針值是否為NULL。防止使用指針值為NULL的內(nèi)存。?
??【規(guī)則2】不要忘記為數(shù)組和動(dòng)態(tài)內(nèi)存賦初值。防止將未被初始化的內(nèi)存作為右值使用。?
??【規(guī)則3】避免數(shù)組或指針的下標(biāo)越界,特別要當(dāng)心發(fā)生“多1”或“少1”操作。?
??【規(guī)則4】動(dòng)態(tài)內(nèi)存的申請(qǐng)和釋放必須配對(duì),防止內(nèi)存泄漏。?
??【規(guī)則5】用free或delete釋放了內(nèi)存之后,立即將指針配置為NULL,防止產(chǎn)生“野指針”。?
??3、指針和數(shù)組的對(duì)比?
??C++/C程式中,指針和數(shù)組在不少地方能夠相互替換著用,讓人產(chǎn)生一種錯(cuò)覺,以為兩者是等價(jià)的。?
??數(shù)組要么在靜態(tài)存儲(chǔ)區(qū)被創(chuàng)建(如全局?jǐn)?shù)組),要么在棧上被創(chuàng)建。數(shù)組名對(duì)應(yīng)著(而不是指向)一塊內(nèi)存,其地址和容量在生命期內(nèi)保持不變,只有數(shù)組的內(nèi)容能夠改變。?
??指針能夠隨時(shí)指向任意類型的內(nèi)存塊,他的特征是“可變”,所以我們常用指針來(lái)操作動(dòng)態(tài)內(nèi)存。指針遠(yuǎn)比數(shù)組靈活,但也更危險(xiǎn)。?
??下面以字符串為例比較指針和數(shù)組的特性。?
??3.1?修改內(nèi)容?
??示例3-1中,字符數(shù)組a的容量是6個(gè)字符,其內(nèi)容為hello。a的內(nèi)容能夠改變,如a[0]=??X?。指
針p指向常量字符串“world”(位于靜態(tài)存儲(chǔ)區(qū),內(nèi)容為world),常量字符串的內(nèi)容是不能夠被修改的。從語(yǔ)法上看,編譯器并不覺得語(yǔ)句p[0]=??X?有什么不妥,但是該語(yǔ)句企圖修改常量字符串的內(nèi)容而導(dǎo)致運(yùn)行錯(cuò)誤。
char?a[]?=?“hello”;?a[0]?=??X?;
cout?<<?a?<<?endl;
char?*p?=?“world”;?//?注意p指向常量字符串?p[0]?=??X?;?//?編譯器不能發(fā)現(xiàn)該錯(cuò)誤?cout?<<?p?<<?endl;
??????示例3.1?修改數(shù)組和指針的內(nèi)容?
??3.2?內(nèi)容復(fù)制和比較?
??不能對(duì)數(shù)組名進(jìn)行直接復(fù)制和比較。示例7-3-2中,若想把數(shù)組a的內(nèi)容復(fù)制給數(shù)組b,不能用語(yǔ)句?b?=?a?,否則將產(chǎn)生編譯錯(cuò)誤。應(yīng)該用標(biāo)準(zhǔn)庫(kù)函數(shù)strcpy進(jìn)行復(fù)制。同理,比較b和a的內(nèi)容是否相同,不能用if(b==a)?來(lái)判斷,應(yīng)該用標(biāo)準(zhǔn)庫(kù)函數(shù)strcmp進(jìn)行比較。?
??語(yǔ)句p?=?a?并不能把a(bǔ)的內(nèi)容復(fù)制指針p,而是把a(bǔ)的地址賦給了p。要想復(fù)制a的內(nèi)容,能夠先用庫(kù)函數(shù)malloc為p申請(qǐng)一塊容量為strlen(a)+1個(gè)字符的內(nèi)存,再用strcpy進(jìn)行字符串復(fù)制。同理,語(yǔ)句if(p==a)?比較的不是內(nèi)容而是地址,應(yīng)該用庫(kù)函數(shù)strcmp來(lái)比較。
//?數(shù)組…
char?a[]?=?"hello";?char?b[10];
strcpy(b,?a);?//?不能用?b?=?a;
if(strcmp(b,?a)?==?0)?//?不能用?if?(b?==?a)?…
//?指針…
int?len?=?strlen(a);
char?*p?=?(char?*)malloc(sizeof(char)*(len+1));?strcpy(p,a);?//?不要用?p?=?a;
if(strcmp(p,?a)?==?0)?//?不要用?if?(p?==?a)?…
???????示例3.2?數(shù)組和指針的內(nèi)容復(fù)制和比較?
??3.3?計(jì)算內(nèi)存容量?
??用運(yùn)算符sizeof能夠計(jì)算出數(shù)組的容量(字節(jié)數(shù))。示例7-3-3(a)中,sizeof(a)的值是12(注意別忘了??)。指針p指向a,但是sizeof(p)的值卻是4。這是因?yàn)閟izeof(p)得到的是個(gè)指針變量的字節(jié)數(shù),相當(dāng)于sizeof(char*),而不是p所指的內(nèi)存容量。C++/C語(yǔ)言沒有辦法知道指針?biāo)傅膬?nèi)存容量,除非在申請(qǐng)內(nèi)存時(shí)記住他。?
??注意當(dāng)數(shù)組作為函數(shù)的參數(shù)進(jìn)行傳遞時(shí),該數(shù)組自動(dòng)退化為同類型的指針。示例7-3-3(b)中,不論數(shù)組a的容量是多少,sizeof(a)始終等于sizeof(char?*)。
char?a[]?=?"hello?world";?char?*p?=?a;
cout<<?sizeof(a)?<<?endl;?//?12字節(jié)?cout<<?sizeof(p)?<<?endl;?//?4字節(jié)
?????示例3.3(a)?計(jì)算數(shù)組和指針的內(nèi)存容量
void?Func(char?a[100])?{
?cout<<?sizeof(a)?<<?endl;?//?4字節(jié)而不是100字節(jié)?}
?????示例3.3(b)?數(shù)組退化為指針??
??1、內(nèi)存分配方式?
??內(nèi)存分配方式有三種:?
??(1)從靜態(tài)存儲(chǔ)區(qū)域分配。內(nèi)存在程式編譯的時(shí)候就已分配好,這塊內(nèi)存在程式的整個(gè)運(yùn)行期間都存在。例如全局變量,static變量。?
??(2)在棧上創(chuàng)建。在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都能夠在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。?
??(3)?從堆上分配,亦稱動(dòng)態(tài)內(nèi)存分配。程式在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意多少的內(nèi)存,程式員自己負(fù)責(zé)在何時(shí)用free或delete釋放內(nèi)存。動(dòng)態(tài)內(nèi)存的生存期由我們決定,使用很靈活,但問題也最多。?
??2、常見的內(nèi)存錯(cuò)誤及其對(duì)策?
??發(fā)生內(nèi)存錯(cuò)誤是件很麻煩的事情。編譯器不能自動(dòng)發(fā)現(xiàn)這些錯(cuò)誤,通常是在程式運(yùn)行時(shí)才能捕獲到。而這些錯(cuò)誤大多沒有明顯的癥狀,時(shí)隱時(shí)現(xiàn),增加了改錯(cuò)的難度。有時(shí)用戶怒氣沖沖地把您找來(lái),程式卻沒有發(fā)生任何問題,您一走,錯(cuò)誤又發(fā)作了。?常見的內(nèi)存錯(cuò)誤及其對(duì)策如下:?
??*?內(nèi)存分配未成功,卻使用了它。?
??編程新手常犯這種錯(cuò)誤,因?yàn)樗麄儧]有意識(shí)到內(nèi)存分配會(huì)不成功。常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。假如指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(p!=NULL)進(jìn)行?
??檢查。假如是用malloc或new來(lái)申請(qǐng)內(nèi)存,應(yīng)該用if(p==NULL)?或if(p!=NULL)進(jìn)行防錯(cuò)處理。?
??*?內(nèi)存分配雖然成功,但是尚未初始化就引用它。?
??犯這種錯(cuò)誤主要有兩個(gè)起因:一是沒有初始化的觀念;二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯(cuò)誤(例如數(shù)組)。?內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn),盡管有些時(shí)候?yàn)榱阒?#xff0c;我們寧可信其無(wú)不可信其有。所以無(wú)論用何種方式創(chuàng)建數(shù)組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。?
??*?內(nèi)存分配成功并且已初始化,但操作越過了內(nèi)存的邊界。?
??例如在使用數(shù)組時(shí)經(jīng)常發(fā)生下標(biāo)“多1”或“少1”的操作。特別是在for循環(huán)語(yǔ)句中,循環(huán)次數(shù)很容易搞錯(cuò),導(dǎo)致數(shù)組操作越界。
*?忘記了釋放內(nèi)存,造成內(nèi)存泄露。?
??含有這種錯(cuò)誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存。剛開始時(shí)系統(tǒng)的內(nèi)存充足,您看不到錯(cuò)誤。終有一次程式突然死掉,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡。?
??動(dòng)態(tài)內(nèi)存的申請(qǐng)和釋放必須配對(duì),程式中malloc和free的使用次數(shù)一定要相同,否則肯定有錯(cuò)誤(new/delete同理)。?
??*?釋放了內(nèi)存卻繼續(xù)使用它。??
??有三種情況:?
??(1)程式中的對(duì)象調(diào)用關(guān)系過于復(fù)雜,實(shí)在難以搞清楚某個(gè)對(duì)象究竟是否已釋放了內(nèi)存,此時(shí)應(yīng)該重新設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),從根本上解決對(duì)象管理的混亂局面。?
??(2)函數(shù)的return語(yǔ)句寫錯(cuò)了,注意不要返回指向“棧內(nèi)存”的“指針”或“引用”,因?yàn)樵搩?nèi)存在函數(shù)體結(jié)束時(shí)被自動(dòng)銷毀。?
??(3)使用free或delete釋放了內(nèi)存后,沒有將指針配置為NULL。導(dǎo)致產(chǎn)生“野指針”。?
??【規(guī)則1】用malloc或new申請(qǐng)內(nèi)存之后,應(yīng)該立即檢查指針值是否為NULL。防止使用指針值為NULL的內(nèi)存。?
??【規(guī)則2】不要忘記為數(shù)組和動(dòng)態(tài)內(nèi)存賦初值。防止將未被初始化的內(nèi)存作為右值使用。?
??【規(guī)則3】避免數(shù)組或指針的下標(biāo)越界,特別要當(dāng)心發(fā)生“多1”或“少1”操作。?
??【規(guī)則4】動(dòng)態(tài)內(nèi)存的申請(qǐng)和釋放必須配對(duì),防止內(nèi)存泄漏。?
??【規(guī)則5】用free或delete釋放了內(nèi)存之后,立即將指針配置為NULL,防止產(chǎn)生“野指針”。?
??3、指針和數(shù)組的對(duì)比?
??C++/C程式中,指針和數(shù)組在不少地方能夠相互替換著用,讓人產(chǎn)生一種錯(cuò)覺,以為兩者是等價(jià)的。?
??數(shù)組要么在靜態(tài)存儲(chǔ)區(qū)被創(chuàng)建(如全局?jǐn)?shù)組),要么在棧上被創(chuàng)建。數(shù)組名對(duì)應(yīng)著(而不是指向)一塊內(nèi)存,其地址和容量在生命期內(nèi)保持不變,只有數(shù)組的內(nèi)容能夠改變。?
??指針能夠隨時(shí)指向任意類型的內(nèi)存塊,他的特征是“可變”,所以我們常用指針來(lái)操作動(dòng)態(tài)內(nèi)存。指針遠(yuǎn)比數(shù)組靈活,但也更危險(xiǎn)。?
??下面以字符串為例比較指針和數(shù)組的特性。?
??3.1?修改內(nèi)容?
??示例3-1中,字符數(shù)組a的容量是6個(gè)字符,其內(nèi)容為hello。a的內(nèi)容能夠改變,如a[0]=??X?。指
針p指向常量字符串“world”(位于靜態(tài)存儲(chǔ)區(qū),內(nèi)容為world),常量字符串的內(nèi)容是不能夠被修改的。從語(yǔ)法上看,編譯器并不覺得語(yǔ)句p[0]=??X?有什么不妥,但是該語(yǔ)句企圖修改常量字符串的內(nèi)容而導(dǎo)致運(yùn)行錯(cuò)誤。
char?a[]?=?“hello”;?a[0]?=??X?;
cout?<<?a?<<?endl;
char?*p?=?“world”;?//?注意p指向常量字符串?p[0]?=??X?;?//?編譯器不能發(fā)現(xiàn)該錯(cuò)誤?cout?<<?p?<<?endl;
??????示例3.1?修改數(shù)組和指針的內(nèi)容?
??3.2?內(nèi)容復(fù)制和比較?
??不能對(duì)數(shù)組名進(jìn)行直接復(fù)制和比較。示例7-3-2中,若想把數(shù)組a的內(nèi)容復(fù)制給數(shù)組b,不能用語(yǔ)句?b?=?a?,否則將產(chǎn)生編譯錯(cuò)誤。應(yīng)該用標(biāo)準(zhǔn)庫(kù)函數(shù)strcpy進(jìn)行復(fù)制。同理,比較b和a的內(nèi)容是否相同,不能用if(b==a)?來(lái)判斷,應(yīng)該用標(biāo)準(zhǔn)庫(kù)函數(shù)strcmp進(jìn)行比較。?
??語(yǔ)句p?=?a?并不能把a(bǔ)的內(nèi)容復(fù)制指針p,而是把a(bǔ)的地址賦給了p。要想復(fù)制a的內(nèi)容,能夠先用庫(kù)函數(shù)malloc為p申請(qǐng)一塊容量為strlen(a)+1個(gè)字符的內(nèi)存,再用strcpy進(jìn)行字符串復(fù)制。同理,語(yǔ)句if(p==a)?比較的不是內(nèi)容而是地址,應(yīng)該用庫(kù)函數(shù)strcmp來(lái)比較。
//?數(shù)組…
char?a[]?=?"hello";?char?b[10];
strcpy(b,?a);?//?不能用?b?=?a;
if(strcmp(b,?a)?==?0)?//?不能用?if?(b?==?a)?…
//?指針…
int?len?=?strlen(a);
char?*p?=?(char?*)malloc(sizeof(char)*(len+1));?strcpy(p,a);?//?不要用?p?=?a;
if(strcmp(p,?a)?==?0)?//?不要用?if?(p?==?a)?…
???????示例3.2?數(shù)組和指針的內(nèi)容復(fù)制和比較?
??3.3?計(jì)算內(nèi)存容量?
??用運(yùn)算符sizeof能夠計(jì)算出數(shù)組的容量(字節(jié)數(shù))。示例7-3-3(a)中,sizeof(a)的值是12(注意別忘了??)。指針p指向a,但是sizeof(p)的值卻是4。這是因?yàn)閟izeof(p)得到的是個(gè)指針變量的字節(jié)數(shù),相當(dāng)于sizeof(char*),而不是p所指的內(nèi)存容量。C++/C語(yǔ)言沒有辦法知道指針?biāo)傅膬?nèi)存容量,除非在申請(qǐng)內(nèi)存時(shí)記住他。?
??注意當(dāng)數(shù)組作為函數(shù)的參數(shù)進(jìn)行傳遞時(shí),該數(shù)組自動(dòng)退化為同類型的指針。示例7-3-3(b)中,不論數(shù)組a的容量是多少,sizeof(a)始終等于sizeof(char?*)。
char?a[]?=?"hello?world";?char?*p?=?a;
cout<<?sizeof(a)?<<?endl;?//?12字節(jié)?cout<<?sizeof(p)?<<?endl;?//?4字節(jié)
?????示例3.3(a)?計(jì)算數(shù)組和指針的內(nèi)存容量
void?Func(char?a[100])?{
?cout<<?sizeof(a)?<<?endl;?//?4字節(jié)而不是100字節(jié)?}
?????示例3.3(b)?數(shù)組退化為指針??
總結(jié)
- 上一篇: C++中的内存管理(new、delete
- 下一篇: tcp连接探测Keepalive和心跳包