一個由C/C++編譯的程序占用的內(nèi)存分為以下幾個部分
1哲戚、棧區(qū)(stack)—由編譯器自動分配釋放,存放函數(shù)參數(shù)值艾岂,局部變量的值等顺少。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
2澳盐、堆區(qū)(heap) — 一般由程序員分配釋放祈纯, 若程序員不釋放,程序結(jié)束時可能由OS回收 叼耙。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事腕窥,分配方式倒是類似于鏈表。
3筛婉、全局區(qū)(靜態(tài)區(qū))(static)—簇爆,全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域爽撒, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域入蛆。 -程序結(jié)束后由系統(tǒng)釋放
4、文字常量區(qū) —常量字符串就是放在這里的硕勿。程序結(jié)束后由系統(tǒng)釋放
5哨毁、程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。
二源武、例子程序
這是一個前輩寫的扼褪,非常詳細(xì)
//main.cpp
int a = 0; 全局初始化區(qū)
char *p1; 全局未初始化區(qū)
main()
{
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456在常量區(qū),p3在棧上粱栖。
static int c =0话浇; 全局(靜態(tài))初始化區(qū)
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20字節(jié)的區(qū)域就在堆區(qū)。
strcpy(p1, "123456"); 123456放在常量區(qū)闹究,編譯器可能會將它與p3所指向的"123456"優(yōu)化成一個地方幔崖。
}
二、堆和棧的理論知識
2.1申請方式
stack:
由系統(tǒng)自動分配渣淤。 例如赏寇,聲明在函數(shù)中一個局部變量 int b; 系統(tǒng)自動在棧中為b開辟空間
heap:
需要程序員自己申請,并指明大小价认,在c中malloc函數(shù)
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);
但是注意p1蹋订、p2本身是在棧中的。
2.2
申請后系統(tǒng)的響應(yīng)
棧:只要棧的剩余空間大于所申請空間刻伊,系統(tǒng)將為程序提供內(nèi)存露戒,否則將報異常提示棧溢出椒功。
堆:首先應(yīng)該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請時智什,
會遍歷該鏈表动漾,尋找第一個空間大于所申請空間的堆結(jié)點,然后將該結(jié)點從空閑結(jié)點鏈表中刪除荠锭,并將該結(jié)點的空間分配給程序旱眯,另外,對于大多數(shù)系統(tǒng)证九,會在這塊內(nèi)存空間中的首地址處記錄本次分配的大小删豺,這樣,代碼中的 delete語句才能正確的釋放本內(nèi)存空間愧怜。另外呀页,由于找到的堆結(jié)點的大小不一定正好等于申請的大小,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中拥坛。
2.3申請大小的限制
棧:在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)蓬蝶,是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的猜惋,在 WINDOWS下丸氛,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數(shù))著摔,如果申請的空間超過棧的剩余空間時缓窜,將提示overflow。因此谍咆,能從棧獲得的空間較小禾锤。
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域卧波。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的,自然是不連續(xù)的庇茫,而鏈表的遍歷方向是由低地址向高地址港粱。堆的大小受限于計算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見旦签,堆獲得的空間比較靈活查坪,也比較大。
2.4申請效率的比較:
棧由系統(tǒng)自動分配宁炫,速度較快偿曙。但程序員是無法控制的。
堆是由new分配的內(nèi)存羔巢,一般速度比較慢望忆,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便.
另外罩阵,在WINDOWS下,最好的方式是用VirtualAlloc分配內(nèi)存启摄,他不是在堆稿壁,也不是在棧是直接在進(jìn)程的地址空間中保留一快內(nèi)存,雖然用起來最不方便歉备。但是速度快傅是,也最靈活。
2.5堆和棧中的存儲內(nèi)容
棧: 在函數(shù)調(diào)用時蕾羊,第一個進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址喧笔,然后是函數(shù)的各個參數(shù),在大多數(shù)的C編譯器中龟再,參數(shù)是由右往左入棧的书闸,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的吸申。
當(dāng)本次函數(shù)調(diào)用結(jié)束后梗劫,局部變量先出棧,然后是參數(shù)截碴,最后棧頂指針指向最開始存的地址梳侨,也就是主函數(shù)中的下一條指令,程序由該點繼續(xù)運行日丹。
堆:一般是在堆的頭部用一個字節(jié)存放堆的大小走哺。堆中的具體內(nèi)容有程序員安排。
2.6存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa 是在運行時刻賦值的哲虾;
而bbbbbbbbbbb是在編譯時就確定的丙躏;
但是,在以后的存取中束凑,在棧上的數(shù)組比指針?biāo)赶虻淖址?例如堆)快晒旅。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
對應(yīng)的匯編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中汪诉,在根據(jù)edx讀取字符废恋,顯然慢了。
2.7小結(jié):
堆和棧的區(qū)別可以用如下的比喻來看出:
使用棧就象我們?nèi)ワ堭^里吃飯扒寄,只管點菜(發(fā)出申請)鱼鼓、付錢、和吃(使用)该编,吃飽了就走迄本,不必理會切菜、洗菜等準(zhǔn)備工作和洗碗课竣、刷鍋等掃尾工作嘉赎,他的好處是快捷置媳,但是自由度小。
使用堆就象是自己動手做喜歡吃的菜肴曹阔,比較麻煩半开,但是比較符合自己的口味,而且自由度大赃份。
1寂拆、內(nèi)存分配方面:
堆:一般由程序員分配釋放, 若程序員不釋放抓韩,程序結(jié)束時可能由OS回收 纠永。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式是類似于鏈表谒拴〕⒔可能用到的關(guān)鍵字如下:new、malloc英上、delete炭序、free等等。
棧:由編譯器(Compiler)自動分配釋放苍日,存放函數(shù)的參數(shù)值惭聂,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧相恃。
2辜纲、申請方式方面:
堆:需要程序員自己申請,并指明大小拦耐。在c中malloc函數(shù)如p1 = (char *)malloc(10)耕腾;在C++中用new運算符,但是注意p1杀糯、p2本身是在棧中的扫俺。因為他們還是可以認(rèn)為是局部變量。
棧:由系統(tǒng)自動分配固翰。 例如狼纬,聲明在函數(shù)中一個局部變量 int b;系統(tǒng)自動在棧中為b開辟空間倦挂。
3畸颅、系統(tǒng)響應(yīng)方面:
堆:操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表担巩,當(dāng)系統(tǒng)收到程序的申請時方援,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結(jié)點涛癌,然后將該結(jié)點從空閑結(jié)點鏈表中刪除犯戏,并將該結(jié)點的空間分配給程序,另外,對于大多數(shù)系統(tǒng)泊碑,會在這塊內(nèi)存空間中的首地址處記錄本次分配的大小你弦,這樣代碼中的delete語句才能正確的釋放本內(nèi)存空間。另外由于找到的堆結(jié)點的大小不一定正好等于申請的大小呀非,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中坚俗。
棧:只要棧的剩余空間大于所申請空間,系統(tǒng)將為程序提供內(nèi)存岸裙,否則將報異常提示棧溢出猖败。
4、大小限制方面:
堆:是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)降允,是不連續(xù)的內(nèi)存區(qū)域恩闻。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的,自然是不連續(xù)的剧董,而鏈表的遍歷方向是由低地址向高地址幢尚。堆的大小受限于計算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見翅楼,堆獲得的空間比較靈活尉剩,也比較大。
棧:在Windows下, 棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)犁嗅,是一塊連續(xù)的內(nèi)存的區(qū)域边涕。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在WINDOWS下褂微,棧的大小是固定的(是一個編譯時就確定的常數(shù))功蜓,如果申請的空間超過棧的剩余空間時,將提示overflow宠蚂。因此式撼,能從棧獲得的空間較小。
5求厕、效率方面:
堆:是由new分配的內(nèi)存著隆,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片呀癣,不過用起來最方便美浦,另外,在WINDOWS下项栏,最好的方式是用 VirtualAlloc分配內(nèi)存浦辨,他不是在堆,也不是在棧是直接在進(jìn)程的地址空間中保留一快內(nèi)存沼沈,雖然用起來最不方便流酬。但是速度快币厕,也最靈活。
棧:由系統(tǒng)自動分配芽腾,速度較快旦装。但程序員是無法控制的。
6摊滔、存放內(nèi)容方面:
堆:一般是在堆的頭部用一個字節(jié)存放堆的大小阴绢。堆中的具體內(nèi)容有程序員安排。
棧:在函數(shù)調(diào)用時第一個進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址然后是函數(shù)的各個參數(shù)艰躺,在大多數(shù)的C編譯器中旱函,參數(shù)是由右往左入棧,然后是函數(shù)中的局部變量描滔。 注意: 靜態(tài)變量是不入棧的棒妨。當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧含长,然后是參數(shù)券腔,最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令拘泞,程序由該點繼續(xù)運行纷纫。
7、存取效率方面:
堆:char *s1 = "Hellow Word"陪腌;是在編譯時就確定的辱魁;
棧:char s1[] = "Hellow Word"; 是在運行時賦值的诗鸭;用數(shù)組比用指針?biāo)俣纫煲恍┤敬兀驗橹羔樤诘讓訁R編中需要用edx寄存器中轉(zhuǎn)一下,而數(shù)組在棧上直接讀取强岸。
C語言內(nèi)存管理
【規(guī)則1】用malloc或new申請內(nèi)存之后锻弓,應(yīng)該立即檢查指針值是否為NULL。防止使用指針值為NULL的內(nèi)存蝌箍。
【規(guī)則2】不要忘記為數(shù)組和動態(tài)內(nèi)存賦初值青灼。防止將未被初始化的內(nèi)存作為右值使用。
【規(guī)則3】避免數(shù)組或指針的下標(biāo)越界妓盲,特別要當(dāng)心發(fā)生“多1”或者“少1”操作杂拨。
【規(guī)則4】動態(tài)內(nèi)存的申請與釋放必須配對,防止內(nèi)存泄漏悯衬。
【規(guī)則5】用free或delete釋放了內(nèi)存之后弹沽,立即將指針設(shè)置為NULL,防止產(chǎn)生“野指針”。
常見的內(nèi)存錯誤及其對策如下:
1.內(nèi)存分配未成功贷币,卻使用了它。
編程新手常犯這種錯誤亏狰,因為他們沒有意識到內(nèi)存分配會不成功役纹。常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL暇唾。如果指針p是函數(shù)的參數(shù)促脉,那么在函數(shù)的入口處用assert(p!=NULL)進(jìn)行檢查。如果是用malloc或new來申請內(nèi)存策州,應(yīng)該用if(p==NULL)或if(p!=NULL)進(jìn)行防錯處理瘸味。
2.內(nèi)存分配雖然成功,但是尚未初始化就引用它够挂。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念旁仿;二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯誤(例如數(shù)組)孽糖。
內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn)枯冈,盡管有些時候為零值,我們寧可信其無不可信其有办悟。所以無論用何種方式創(chuàng)建數(shù)組尘奏,都別忘了賦初值,即便是賦零值也不可省略病蛉,不要嫌麻煩炫加。
3.內(nèi)存分配成功并且已經(jīng)初始化,但操作越過了內(nèi)存的邊界铺然。
例如在使用數(shù)組時經(jīng)常發(fā)生下標(biāo)“多1”或者“少1”的操作俗孝。特別是在for循環(huán)語句中,循環(huán)次數(shù)很容易搞錯魄健,導(dǎo)致數(shù)組操作越界驹针。
4.忘記了釋放內(nèi)存,造成內(nèi)存泄露诀艰。
含有這種錯誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存柬甥。剛開始時系統(tǒng)的內(nèi)存充足,你看不到錯誤其垄。終有一次程序突然死掉苛蒲,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡。
動態(tài)內(nèi)存的申請與釋放必須配對绿满,程序中malloc與free的使用次數(shù)一定要相同臂外,否則肯定有錯誤(new/delete同理)。
5.釋放了內(nèi)存卻繼續(xù)使用它。
有三種情況:
(1)程序中的對象調(diào)用關(guān)系過于復(fù)雜漏健,實在難以搞清楚某個對象究竟是否已經(jīng)釋放了內(nèi)存嚎货,此時應(yīng)該重新設(shè)計數(shù)據(jù)結(jié)構(gòu),從根本上解決對象管理的混亂局面蔫浆。
(2)函數(shù)的return語句寫錯了殖属,注意不要返回指向“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)束時被自動銷毀瓦盛。
(3)使用free或delete釋放了內(nèi)存后洗显,沒有將指針設(shè)置為NULL。導(dǎo)致產(chǎn)生“野指針”