程序的內(nèi)存布局以及棧、堆原理

                                                     參考自《程序員的自我修養(yǎng)》

程序的內(nèi)存布局

在學(xué)習(xí)內(nèi)存布局之前今豆,建議先了解一下程序是如何映射到內(nèi)存中的
〉坪現(xiàn)代的應(yīng)用程序都運(yùn)行在一個(gè)內(nèi)存空間里撇簿。在32位的系統(tǒng)里聂渊,這個(gè)內(nèi)存空間擁有4GB(2的32次方)的尋址能力。在平坦內(nèi)存模型中四瘫,整個(gè)內(nèi)存空間是一個(gè)統(tǒng)一的地址空間汉嗽,用戶可以使用一個(gè)指針訪問任意內(nèi)存位置。例如:

int *p = (int *)0x12345678;
++*p;

這段代碼展示了如何直接讀寫指定地址的內(nèi)存數(shù)據(jù)莲组。但是诊胞,大多數(shù)操作系統(tǒng)的內(nèi)存空間實(shí)際上是不平坦的,它們一般都會(huì)將系統(tǒng)內(nèi)存分為兩部分锹杈,一部分給內(nèi)核使用一部分給應(yīng)用程序使用撵孤。我們成為內(nèi)核空間用戶空間。將用戶空間和內(nèi)核空間置于這種非對(duì)稱訪問機(jī)制下有很好的安全性竭望,能有效抵御惡意用戶的窺探邪码,也能防止質(zhì)量低劣的用戶程序的侵害,從而使系統(tǒng)運(yùn)行得更穩(wěn)定可靠咬清。

內(nèi)核空間

內(nèi)核空間是給內(nèi)核使用的闭专,應(yīng)用程序無法訪問這一空間。內(nèi)核空間是為了讓系統(tǒng)一部分核心軟件獨(dú)立于普通應(yīng)用程序旧烧,運(yùn)行在較高的特權(quán)級(jí)別上影钉,它們駐留在被保護(hù)的內(nèi)存空間上,擁有訪問硬件設(shè)備的所有權(quán)限掘剪。

用戶空間

用戶空間是給應(yīng)用程序使用的平委。用戶空間的代碼運(yùn)行在較低的特權(quán)級(jí)別上,只能看到允許它們使用的部分系統(tǒng)資源夺谁,并且不能使用某些特定的系統(tǒng)功能廉赔,也不能直接訪問內(nèi)核空間和硬件設(shè)備,以及其他一些具體的使用限制匾鸥。用戶空間又可以將地址空間分成如下不同的區(qū)域:

  • 棧:棧用于維護(hù)函數(shù)調(diào)用的上下文蜡塌,離開了棧函數(shù)調(diào)用就沒法實(shí)現(xiàn)。棧通常在用戶空間的最高地址出分配勿负,地址是由高往低走的馏艾。
  • 堆:堆是用來容納容納應(yīng)用程序動(dòng)態(tài)分配的內(nèi)存區(qū)域,當(dāng)程序使用malloc或new分配內(nèi)存時(shí),得到的內(nèi)存來自堆里琅摩。堆通常存在于棧的下方(低地址方向)厚者,在某些時(shí)候,堆可能沒有固定統(tǒng)一的存儲(chǔ)區(qū)域迫吐。堆一般比棧大很多。
  • 可執(zhí)行文件映像:這里存儲(chǔ)著可執(zhí)行文件在內(nèi)存里的映像账忘,由裝載器在裝載時(shí)將可執(zhí)行文件的內(nèi)存讀取到這里志膀。

可執(zhí)行文件又可分為數(shù)據(jù)段和代碼段。數(shù)據(jù)段DATA段和BSS段組成鳖擒。DATA是已初始化的數(shù)據(jù)段溉浙,占文件空間,也占物理內(nèi)存蒋荚;BSS未初始化的數(shù)據(jù)段戳稽,但其實(shí)并不包含數(shù)據(jù),僅維護(hù)一個(gè)開始地址和結(jié)束地址期升,BSS段的數(shù)據(jù)默認(rèn)都是零惊奇,所以在二進(jìn)制可執(zhí)行文件的文件頭內(nèi),其實(shí)是不存在BSS段的播赁,所以BSS不占文件空間颂郎,只占物理內(nèi)存。代碼段是存放程序代碼的地方容为,同時(shí)也是只讀的乓序,比如我們的char *str = “hello world”; 就是存放在只讀數(shù)據(jù)區(qū),其實(shí)和代碼段在一起坎背,因此替劈,在程序中我們不可以修改這個(gè)字符串。

  • 保留區(qū):保留區(qū)并不是單一的內(nèi)存區(qū)域得滤,而是內(nèi)存中受到保護(hù)而禁止訪問的內(nèi)存區(qū)域的總稱陨献,例如,大多數(shù)操作系統(tǒng)里耿戚,極小的地址通常都是不允許訪問的湿故,如NULL。通常C語言將無效指針賦值為0也是出于這個(gè)考慮膜蛔,因?yàn)?地址正常情況下不可能有有效的可訪問數(shù)據(jù)坛猪。
    以下是一個(gè)典型的進(jìn)程內(nèi)存布局:


    內(nèi)存布局.png

    圖中有一個(gè)前面沒有提到的動(dòng)態(tài)鏈接庫映射區(qū)(dynamic libraries),這個(gè)區(qū)域用于映射裝載的動(dòng)態(tài)鏈接庫皂股。如果程序依賴其他共享庫墅茉,共享庫將被載入該空間。
    圖中的箭頭可以看出棧是由高地址向低地址增長的,而堆是由低地址向高地址增長的就斤。當(dāng)它們大小不夠用時(shí)悍募,就會(huì)按上面的方法增長方向擴(kuò)大自身尺寸,直到未使用空間被用完為止洋机。

棧與函數(shù)調(diào)用

什么是棧

棧(stack)其實(shí)本身是一種數(shù)據(jù)結(jié)構(gòu)坠宴,在經(jīng)典計(jì)算機(jī)中,棧被定義為一個(gè)特殊的容器绷旗,是一個(gè)具有先進(jìn)后出屬性的動(dòng)態(tài)內(nèi)存區(qū)域喜鼓。程序可以將數(shù)據(jù)壓入棧中(入棧,push)衔肢,也可以將已壓入棧中的數(shù)據(jù)彈出(出棧庄岖,pop),但它必須遵循一個(gè)原則角骤,那就是先進(jìn)后出(First In Last Out隅忿,F(xiàn)ILO)。棧的增長方向是向下增長邦尊,即由高地址向低地址方向背桐。以下是一個(gè)棧的示例:

棧結(jié)構(gòu).png

這里的棧底0xbfffffff,而esp寄存器標(biāo)明了棧頂胳赌,地址為0xbffffff4牢撼。壓棧會(huì)導(dǎo)致esp減小,出棧會(huì)導(dǎo)致esp增大疑苫。反過來說熏版,改變esp的值,會(huì)改變椇床簦空間的大小撼短。

棧有什么作用

用于維護(hù)函數(shù)調(diào)用的上下文,離開了棧函數(shù)調(diào)用沒法實(shí)現(xiàn)挺勿。棧中保存了一個(gè)函數(shù)調(diào)用所需要的維護(hù)信息曲横,通常稱為堆棧幀活動(dòng)記錄。堆棧棧包括的內(nèi)容:

  • 函數(shù)的返回地址和參數(shù)
  • 臨時(shí)變量
  • 保存的上下文不瓶,例如函數(shù)調(diào)用前后保持不變的寄存器禾嫉。
函數(shù)調(diào)用過程
  • 把所有的參數(shù)壓入棧
  • 把當(dāng)前指令的下一條指令的地址壓入棧中(函數(shù)的返回地址)
  • 跳轉(zhuǎn)到函數(shù)體執(zhí)行
  • 棧幀調(diào)整

具體包括:
1、保存當(dāng)前棧幀狀態(tài)值蚊丐,已備后面恢復(fù)本棧幀時(shí)使用(EBP入棧)熙参。
2、將當(dāng)前棧幀切換到新棧幀(將ESP值裝入EBP麦备,更新棧幀底部)孽椰。
3昭娩、給新棧幀分配空間(把ESP減去所需空間的大小,抬高棧頂)黍匾。

函數(shù)的返回

函數(shù)返回的步驟如下:

  • 保存返回值栏渺,通常將函數(shù)的返回值保存在寄存器EAX中。
  • 彈出當(dāng)前幀锐涯,恢復(fù)上一個(gè)棧幀磕诊。

具體包括:
1、在堆棧平衡的基礎(chǔ)上纹腌,給ESP加上棧幀的大小秀仲,降低棧頂,回收當(dāng)前棧幀的空間壶笼。
2、將當(dāng)前棧幀底部保存的前棧幀EBP值彈入EBP寄存器雁刷,恢復(fù)出上一個(gè)棧幀覆劈。
3、將函數(shù)返回地址彈給EIP寄存器沛励。

  • 跳轉(zhuǎn):按照函數(shù)返回地址跳回母函數(shù)中繼續(xù)執(zhí)行责语。

堆與內(nèi)存管理

什么是堆

堆內(nèi)存是區(qū)別于棧區(qū)、全局?jǐn)?shù)據(jù)區(qū)和代碼區(qū)的另一個(gè)內(nèi)存區(qū)域目派。堆允許程序在運(yùn)行時(shí)動(dòng)態(tài)地申請(qǐng)某個(gè)大小的內(nèi)存空間坤候。光有棧對(duì)于程序還遠(yuǎn)遠(yuǎn)不夠,因?yàn)闂I系臄?shù)據(jù)在函數(shù)返回的時(shí)候就會(huì)被釋放掉企蹭,所以無法將數(shù)據(jù)傳遞至函數(shù)外部白筹。而全局變量沒有辦法動(dòng)態(tài)地產(chǎn)生,只能在編譯的時(shí)候定義谅摄。在這種情況下徒河,堆(Heap)是唯一的選擇。
是一塊巨大的內(nèi)存空間送漠,常常占據(jù)整個(gè)虛擬內(nèi)存的絕大部分空間顽照。在這片空間里,程序可以申請(qǐng)一塊連續(xù)的內(nèi)存闽寡,并自由地使用代兵,這塊內(nèi)存在程序主動(dòng)放棄之前都會(huì)一直保持有效。

內(nèi)存分配

程序中使用malloc爷狈、new等內(nèi)存分配函數(shù)獲取內(nèi)存即是從堆中分配內(nèi)存植影。從堆中分配的內(nèi)存需要程序員手動(dòng)釋放,如果不釋放淆院,而系統(tǒng)內(nèi)存管理器又不自動(dòng)回收這些堆內(nèi)存的話(實(shí)現(xiàn)這一項(xiàng)功能的系統(tǒng)很少)何乎,那就一直被占用句惯。如果一直申請(qǐng)堆內(nèi)存,而不釋放支救,內(nèi)存會(huì)越來越少抢野,很明顯的結(jié)果是系統(tǒng)變慢或者申請(qǐng)不到新的堆內(nèi)存。而過度的申請(qǐng)堆內(nèi)存各墨,會(huì)導(dǎo)致堆被壓爆指孤,結(jié)果是災(zāi)難性的。我們掌握堆內(nèi)存的權(quán)柄就是返回的指針贬堵,一旦丟掉了指針恃轩,便無法在我們視野內(nèi)釋放它。這便是內(nèi)存泄露黎做。

堆的分配算法
  • 空閑鏈表
    空閑鏈表( Free List)的方法實(shí)際上就是把堆中各個(gè)空閑的塊按照鏈表的方式連接起來,當(dāng)用戶請(qǐng)求一塊空間時(shí),可以遍歷整個(gè)列表,直到找到合適大小的塊并且將它拆分;當(dāng)用戶釋放空間時(shí)將它合并到空閑鏈表中叉跛。
  • 位圖
    針對(duì)空閑鏈表的弊端,另一種分配方式顯得更加穩(wěn)健。這種方式稱為位圖( Bitmap)蒸殿,其核心思想是將整個(gè)堆劃分為大量的塊( block),每個(gè)塊的大小相同筷厘。當(dāng)用戶請(qǐng)求內(nèi)存的時(shí)候,總是分配整數(shù)個(gè)塊的空間給用戶,第一個(gè)塊我們稱為已分配區(qū)域的頭(Head),其余的稱為己分配區(qū)域的主體(Body)。而我們可以使用一個(gè)整數(shù)數(shù)組來記錄塊的使用情況,由于每個(gè)塊只有頭/主體空閑三種狀態(tài),因此僅僅需要兩位即可表示一個(gè)塊,因此稱為位圖宏所。
  • 對(duì)象池
    以上介紹的堆管理方法是最為基本的兩種,實(shí)際上在一些場合,被分配對(duì)象的大小是較為固定的幾個(gè)值,這時(shí)候我們可以針對(duì)這樣的特征設(shè)計(jì)一個(gè)更為高效的堆算法,稱為對(duì)象池酥艳。
    對(duì)象池的思路很簡單,如果每一次分配的空間大小都一樣,那么就可以按照這個(gè)每次請(qǐng)求分配的大小作為一個(gè)單位,把整個(gè)堆空間劃分為大量的小塊,每次請(qǐng)求的時(shí)候只需要找到個(gè)小塊就可以了。
    對(duì)象池的管理方法可以采用空閑鏈表,也可以采用位圖,與它們的區(qū)別僅僅在于它假定了每次請(qǐng)求的都是一個(gè)固定的大小,因此實(shí)現(xiàn)起來很容易爬骤。由于每次總是只請(qǐng)求一個(gè)單位的內(nèi)存,因此請(qǐng)求得到滿足的速度非吵涫快,無須查找一個(gè)足夠大的空間。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末霞玄,一起剝皮案震驚了整個(gè)濱河市骤铃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坷剧,老刑警劉巖劲厌,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異听隐,居然都是意外死亡补鼻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門雅任,熙熙樓的掌柜王于貴愁眉苦臉地迎上來风范,“玉大人,你說我怎么就攤上這事沪么∨鹦觯” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵禽车,是天一觀的道長寇漫。 經(jīng)常有香客問我刊殉,道長,這世上最難降的妖魔是什么州胳? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任记焊,我火速辦了婚禮,結(jié)果婚禮上栓撞,老公的妹妹穿的比我還像新娘遍膜。我一直安慰自己,他們只是感情好瓤湘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布瓢颅。 她就那樣靜靜地躺著,像睡著了一般弛说。 火紅的嫁衣襯著肌膚如雪挽懦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天木人,我揣著相機(jī)與錄音巾兆,去河邊找鬼。 笑死虎囚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔫磨。 我是一名探鬼主播淘讥,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼堤如!你這毒婦竟也來了蒲列?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤搀罢,失蹤者是張志新(化名)和其女友劉穎蝗岖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榔至,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抵赢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唧取。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铅鲤。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枫弟,靈堂內(nèi)的尸體忽然破棺而出邢享,到底是詐尸還是另有隱情,我是刑警寧澤淡诗,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布骇塘,位于F島的核電站伊履,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏款违。R本人自食惡果不足惜唐瀑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奠货。 院中可真熱鬧介褥,春花似錦、人聲如沸递惋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萍虽。三九已至睛廊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杉编,已是汗流浹背超全。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邓馒,地道東北人嘶朱。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像光酣,于是被迫代替她去往敵國和親疏遏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355