淺析C語(yǔ)言中堆和棧的區(qū)別
在計(jì)算機(jī)領(lǐng)域,堆棧是一個(gè)不容忽視的概念,我們編寫(xiě)的C語(yǔ)言程序基本上都要用到。但對(duì)于很多的初學(xué)著來(lái)說(shuō),堆棧是一個(gè)很模糊的概念。
堆棧:一種數(shù)據(jù)結(jié)構(gòu)、一個(gè)在程序運(yùn)行時(shí)用于存放的地方,這可能是很多初學(xué)者的認(rèn)識(shí),因?yàn)槲以?jīng)就是這么想的和匯編語(yǔ)言中的堆棧一詞混為一談。我身邊的一些編程的朋友以及在網(wǎng)上看帖遇到的朋友中有好多也說(shuō)不清堆棧,所以我想有必要給大家分享一下我對(duì)堆棧的看法,有說(shuō)的不對(duì)的地方請(qǐng)朋友們不吝賜教,這對(duì)于大家學(xué)習(xí)會(huì)有很大幫助。
一.前言:
C語(yǔ)言程序經(jīng)過(guò)編譯連接后形成編譯、連接后形成的二進(jìn)制映像文件由棧,堆,數(shù)據(jù)段(由三部分部分組成:只讀數(shù)據(jù)段,已經(jīng)初始化讀寫(xiě)數(shù)據(jù)段,未初始化數(shù)據(jù)段即BBS)和代碼段組成,如下圖所示:
1.棧區(qū)(stack):由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值,局部變量等值。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
2.堆區(qū)(heap):一般由程序員分配釋放,若程序員不釋放,則可能會(huì)引起內(nèi)存泄漏。注堆和數(shù)據(jù)結(jié)構(gòu)中的堆棧不一樣,其類是與鏈表。
3.程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼。
4.數(shù)據(jù)段:由三部分組成:
1>只讀數(shù)據(jù)段:
只讀數(shù)據(jù)段是程序使用的一些不會(huì)被更改的數(shù)據(jù),使用這些數(shù)據(jù)的方式類似查表式的操作,由于這些變量不需要更改,因此只需要放置在只讀存儲(chǔ)器中即可。一般是const修飾的變量以及程序中使用的文字常量一般會(huì)存放在只讀數(shù)據(jù)段中。
2>已初始化的讀寫(xiě)數(shù)據(jù)段:
已初始化數(shù)據(jù)是在程序中聲明,并且具有初值的變量,這些變量需要占用存儲(chǔ)器的空間,在程序執(zhí)行時(shí)它們需要位于可讀寫(xiě)的內(nèi)存區(qū)域內(nèi),并且有初值,以供程序運(yùn)行時(shí)讀寫(xiě)。在程序中一般為已經(jīng)初始化的全局變量,已經(jīng)初始化的靜態(tài)局部變量(static修飾的已經(jīng)初始化的變量)
3>未初始化段(BSS):
未初始化數(shù)據(jù)是在程序中聲明,但是沒(méi)有初始化的變量,這些變量在程序運(yùn)行之前不需要占用存儲(chǔ)器的空間。與讀寫(xiě)數(shù)據(jù)段類似,它也屬于靜態(tài)數(shù)據(jù)區(qū)。但是該段中數(shù)據(jù)沒(méi)有經(jīng)過(guò)初始化。未初始化數(shù)據(jù)段只有在運(yùn)行的初始化階段才會(huì)產(chǎn)生,因此它的大小不會(huì)影響目標(biāo)文件的大小。在程序中一般是沒(méi)有初始化的全局變量和沒(méi)有初始化的靜態(tài)局部變量。
二.堆和棧的區(qū)別
1.申請(qǐng)方式
(1)棧(satck):由系統(tǒng)自動(dòng)分配。例如,聲明在函數(shù)中一個(gè)局部變量int b;系統(tǒng)自動(dòng)在棧中為b開(kāi)辟空間。
(2)堆(heap):需程序員自己申請(qǐng)(調(diào)用malloc,realloc,calloc),并指明大小,并由程序員進(jìn)行釋放。容易產(chǎn)生memory leak.
eg:char p;
p = (char *)malloc(sizeof(char));
但是,p本身是在棧中。
2.申請(qǐng)大小的限制
(1)棧:在windows下棧是向底地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存區(qū)域(它的生長(zhǎng)方向與內(nèi)存的生長(zhǎng)方向相反)。棧的大小是固定的。如果申請(qǐng)的空間超過(guò)棧的剩余空間時(shí),將提示overflow。
(2)堆:堆是高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)(它的生長(zhǎng)方向與內(nèi)存的生長(zhǎng)方向相同),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)使用鏈表來(lái)存儲(chǔ)空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由底地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。
3.系統(tǒng)響應(yīng):
(1)棧:只要棧的空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。
(2)堆:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,但系統(tǒng)收到程序的申請(qǐng)時(shí),會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,另外,對(duì)于大多數(shù)系統(tǒng),會(huì)在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的free語(yǔ)句才能正確的釋放本內(nèi)存空間。另外,找到的堆結(jié)點(diǎn)的大小不一定正好等于申請(qǐng)的大小,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中。
說(shuō)明:對(duì)于堆來(lái)講,對(duì)于堆來(lái)講,頻繁的new/delete勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。對(duì)于棧來(lái)講,則不會(huì)存在這個(gè)問(wèn)題,
4.申請(qǐng)效率
(1)棧由系統(tǒng)自動(dòng)分配,速度快。但程序員是無(wú)法控制的
(2)堆是由malloc分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生碎片,不過(guò)用起來(lái)最方便。
5.堆和棧中的存儲(chǔ)內(nèi)容
(1)棧:在函數(shù)調(diào)用時(shí),第一個(gè)進(jìn)棧的主函數(shù)中后的下一條語(yǔ)句的地址,然后是函數(shù)的各個(gè)參數(shù),參數(shù)是從右往左入棧的,然后是函數(shù)中的局部變量。注:靜態(tài)變量是不入棧的。
當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開(kāi)始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)執(zhí)行。
(2)堆:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。
6.存取效率
(1)堆:char *s1=”hellow tigerjibo”;是在編譯是就確定的
(2)棧:char s1[]=”hellow tigerjibo”;是在運(yùn)行時(shí)賦值的;用數(shù)組比用指針?biāo)俣雀煲恍?,指針在底層匯編中需要用edx寄存器中轉(zhuǎn)一下,而數(shù)組在棧上讀取。
補(bǔ)充:
棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計(jì)算機(jī)會(huì)在底層對(duì)棧提供支持:分配專門(mén)的寄存器存放棧的地址,壓棧出棧都有專門(mén)的指令執(zhí)行,這就決定了棧的效率比較高。堆則是C/C++函數(shù)庫(kù)提供的,它的機(jī)制是很復(fù)雜的,例如為了分配一塊內(nèi)存,庫(kù)函數(shù)會(huì)按照一定的算法(具體的算法可以參考數(shù)據(jù)結(jié)構(gòu)/操作系統(tǒng))在堆內(nèi)存中搜索可用的足夠大小的空間,如果沒(méi)有足夠大小的空間(可能是由于內(nèi)存碎片太多),就有可能調(diào)用系統(tǒng)功能去增加程序數(shù)據(jù)段的內(nèi)存空間,這樣就有機(jī)會(huì)分到足夠大小的內(nèi)存,然后進(jìn)行返回。顯然,堆的效率比棧要低得多。
7.分配方式:
(1)堆都是動(dòng)態(tài)分配的,沒(méi)有靜態(tài)分配的堆。
(2)棧有兩種分配方式:靜態(tài)分配和動(dòng)態(tài)分配。靜態(tài)分配是編譯器完成的,比如局部變量的分配。動(dòng)態(tài)分配由alloca函數(shù)進(jìn)行分配,但是棧的動(dòng)態(tài)分配和堆是不同的。它的動(dòng)態(tài)分配是由編譯器進(jìn)行釋放,無(wú)需手工實(shí)現(xiàn)。
相關(guān)文章
基于MFC實(shí)現(xiàn)單個(gè)文檔的文件讀寫(xiě)
這篇文章主要為大家詳細(xì)介紹了如何基于MFC實(shí)現(xiàn)單個(gè)文檔的文件讀寫(xiě)功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定幫助,感興趣的可以了解一下2022-07-07C++實(shí)現(xiàn)圖形界面時(shí)鐘表盤(pán)代碼
這篇文章主要介紹了C++實(shí)現(xiàn)圖形界面時(shí)鐘表盤(pán)代碼,涉及坐標(biāo)函數(shù)的應(yīng)用及圖形界面程序設(shè)計(jì),需要的朋友可以參考下2014-10-10C++算法計(jì)時(shí)器的實(shí)現(xiàn)示例
本文主要介紹了C++算法計(jì)時(shí)器的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05C++調(diào)用python(執(zhí)行py文件)的全過(guò)程
這篇文章主要給大家介紹了關(guān)于C++調(diào)用python(執(zhí)行py文件)的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-12-12C語(yǔ)言鏈表實(shí)現(xiàn)學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言鏈表實(shí)現(xiàn)學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06C語(yǔ)言動(dòng)態(tài)內(nèi)存分配圖文講解
給數(shù)組分配多大的空間?你是否和初學(xué)C時(shí)的我一樣,有過(guò)這樣的疑問(wèn)。這一期就來(lái)聊一聊動(dòng)態(tài)內(nèi)存的分配,讀完這篇文章,你可能對(duì)內(nèi)存的分配有一個(gè)更好的理解2023-01-01Qt實(shí)現(xiàn)簡(jiǎn)易毛玻璃效果的示例代碼
這篇文章主要介紹了Qt如何利用模糊功能實(shí)現(xiàn)簡(jiǎn)易的毛玻璃效果,并且鼠標(biāo)可以移動(dòng)無(wú)邊框窗口,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-06-06