淺談程序的內(nèi)存分配

內(nèi)存分配

盡管現(xiàn)在的許多高級語言已經(jīng)不需要程序員去直接處理內(nèi)存分配和垃圾回收黄绩,但是內(nèi)存的管理是學習編程過程中的一個很重要的概念,理解相關概念和應用能夠讓我們對編程和計算機有更深的理解蚜退。

一般來講,程序由下面幾部分組成:

:局部變量以及每次函數(shù)調(diào)用時所需要保存的信息都存放在此區(qū)域中利耍。每次函數(shù)調(diào)用時望抽,其返回地址以及調(diào)用者的環(huán)境信息都存放在棧中。然后最近被調(diào)用的函數(shù)在棧上為其局部變量分配存儲空間煌妈。

:堆用于存放程序運行中被動態(tài)分配的內(nèi)存儡羔,大小并不固定。

bss段:也叫未初始化數(shù)據(jù)段璧诵,存放程序中未初始化過的全局變量和靜態(tài)變量汰蜘,在程序開始執(zhí)行之前,內(nèi)核將此段的數(shù)據(jù)初始化為0之宿。

數(shù)據(jù)段:也叫初始化數(shù)據(jù)段族操,存放程序中已經(jīng)明確地初始化的全局變量和靜態(tài)變量。(如C程序中任何函數(shù)之外的聲明)

正文段:存放程序的執(zhí)行代碼,它的大小在程序運行前就已經(jīng)確定色难。通常泼舱,正文段是可以共享的,多個此程序執(zhí)行的進程在內(nèi)存中只需要一個副本枷莉。另外娇昙,正文段常常是只讀的,以防止程序由于意外而修改指令笤妙。該段也可能包含一些只讀的常數(shù)冒掌,如字符串常量等。

對C程序中內(nèi)存布局的探索

所有程序均在Linux CentOS上運行蹲盘。

數(shù)據(jù)段與bss段

運行下面代碼股毫,查看全局變量的地址(注釋為對應地址):

#include <stdio.h>

int a = 0;
int b;
int main(int argc, const char * argv[]) {
    printf("%p\n", &a);     //0x601038
    printf("%p\n", &b);     //0x60103c
    return 0;
}

可以看到,未初始化的b的地址正好在初始化過的a的地址之上召衔,這是巧合嗎铃诬?我們再探索一下:

#include <stdio.h>

int a = 3;
int b;
int c = 5;
int d;
int main(int argc, const char * argv[]) {
    printf("%p\n", &a);     //0x601034 
    printf("%p\n", &b);     //0x601044 
    printf("%p\n", &c);     //0x601038
    printf("%p\n", &d);     //0x601040
    return 0;
}

初始化的ac在低地址,而bd在高地址苍凛,所以說這并不是偶然氧急。

另外,編譯器通常對bss段的處理方式是:只描述大小毫深,不增加目標文件體積吩坝。我們可以使用size命令來看一下編譯后的a.out的各段大小,作為對比哑蔫,我們先對一個沒有聲明任何函數(shù)和變量的程序執(zhí)行size命名:

$ size a.out
   text    data     bss     dec     hex filename
   1129     540       4    1673     689 a.out

接下來钉寝,我們聲明一個未初始化的全局的數(shù)組int a[65535];

#include <stdio.h>
int bss[65535];
int main(int argc, const char * argv[]) {
    bss[0] = 1;
    return 0;
}

執(zhí)行size:

   text    data     bss     dec     hex filename
   1145     540  262176  263861   406b5 a.out

很明顯,bss段變大了闸迷,再看一下a.out的大星陡佟:

$ ls -l a.out
-rwxr-xr-x. 1 thdlee thdlee 8848 5月  11 17:49 a.out

目標文件的大小遠遠小于65535個int,這次直接將數(shù)組賦一個初值int bss[65535] = {1};腥沽,再進行同樣的操作:

$ size a.out
   text    data     bss     dec     hex filename
   1129  262708       4  263841   406a1 a.out
$ ls -l a.out
-rwxr-xr-x. 1 thdlee thdlee 270680 5月  11 17:49 a.out

不出所料逮走,data段增大了,文件也變成了應有的大小今阳。

代碼段

代碼段的內(nèi)存地址可以用函數(shù)指針來檢測:

#include <stdio.h>
void foo() {
}

int a;
int main(int argc, const char * argv[]) {
    printf("%p\n", &a);         \\0x601034
    printf("%p\n", &foo);       \\0x40052d
    return 0;
}

函數(shù)是存放在代碼段的师溅,所以看到函數(shù)的內(nèi)存地址比數(shù)據(jù)區(qū)還要低。另外盾舌,文本區(qū)一般還存放著字符串常量墓臭,我們先來看看下面這個例子:

#include <stdio.h>

int main(int argc, const char * argv[]) {
    char *a = "Hello World!";
    char *b = "Hello World!";
    char *c = "Hello";
    printf("%p\n", a);      \\0x400630
    printf("%p\n", b);      \\0x400630
    printf("%p\n", c);      \\0x40063d
    return 0;
}

從例子中可以看到,字符串的地址在與函數(shù)地址差不多的地方妖谴,而且對于指向相同字符串的指針變量窿锉,它們的地址是相同的。

堆和棧

一般來說,堆是由低地址向高地址增長的嗡载,而棧是由高地址向低地址增長窑多。

按照慣例,我們還是用一個小程序來探索一下:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char * argv[]) {
    int a = 1;
    int b = 2;
    char *p1 = malloc(16);
    char *p2 = malloc(16);
    printf("%p\n", &a);     \\0x7ffe145134dc
    printf("%p\n", &b);     \\0x7ffe145134d8
    printf("%p\n", p1);     \\0x1478010
    printf("%p\n", p2);     \\0x1478030
    free(p1);
    free(p2);
    return 0;
}

根據(jù)變量的聲明順序洼滚,可以看到棧是向下增長怯伊,而堆是向上增長的。這里要注意的一點是判沟,指針p1p2存儲的是指向?qū)Φ牡刂罚撬鼈儍蓚€的存儲位置是在棧上的崭篡。我們再來看看函數(shù)調(diào)用中變量地址的變化挪哄。

#include <stdio.h>

void foo2() {
    int c;
    printf("%p\n", &c);     \\0x7fffb97a99ec
}

void foo1() {
    int b;
    printf("%p\n", &b);     \\0x7fffb97a9a0c
    foo2();
}

void bar() {
    int d;
    printf("%p\n", &d);     \\0x7fffb97a9a0c
}

int main(int argc, const char * argv[]) {
    int a;
    printf("%p\n", &a);     \\0x7fffb97a9a3c
    foo1();
    bar();
    return 0;
}

隨著函數(shù)的開始,棧也開始向下擴展琉闪。當函數(shù)結束時迹炼,分配在棧上的空間也跟著收回。

更進一步地探討

至此颠毙,我們就會對程序中內(nèi)存分配和管理有了一定的了解斯入,但是程序中的內(nèi)存地址是怎么來的呢?它們是計算機中的物理地址嗎蛀蜜?如果不是刻两,那又和物理地址有什么關系呢?

這幾個問題牽扯到了編譯器的和操作系統(tǒng)的一些相關知識滴某,但對這些內(nèi)容深入地探討超出了本文的范圍磅摹,因此本文只能盡量描述清楚其中的關系。

進入正題霎奢,編譯器將代碼轉換為可執(zhí)行程序時户誓,必須為代碼產(chǎn)生的各個值分別分配一個存儲位置。編譯器必須理解值的類型幕侠、長度帝美、可見性和生命周期。編譯器必須考慮一系列對代碼的內(nèi)存處理問題來定義一組約定來解決這些問題晤硕。

為分配存儲悼潭,編譯器必須理解全系統(tǒng)范圍內(nèi)對內(nèi)存分配和使用的約定。編譯器舞箍、操作系統(tǒng)和處理器協(xié)助女责,以確保多個程序能夠以交錯的方式(時間片)安全地執(zhí)行。

除了創(chuàng)建棧创译,對于大多數(shù)語言抵知,編譯器都需要創(chuàng)建堆,以便為動態(tài)分配的數(shù)據(jù)結構提供內(nèi)存。為保證高效地利用內(nèi)存空間刷喜,堆和棧被置于開放空間的兩端残制,彼此相向增長,所以就出現(xiàn)了我們所觀察到的現(xiàn)象掖疮。當然初茶,將堆和棧互換位置效果也是一樣的浊闪。下圖是單個程序編譯后所用地址空間的典型布局恼布,具體的實現(xiàn)和細節(jié)可能會因編譯器和語言的不同而不同:

邏輯地址的空間布局

這只是編譯器的視角所看到的地址空間,編譯器會為編譯的每個程序分配一個獨立的地址空間搁宾,讓程序以為自己擁有獨立的內(nèi)存(其實也讓程序員以為程序有獨立的內(nèi)存)折汞,其實這只是假象。在執(zhí)行程序時盖腿,操作系統(tǒng)會將這些邏輯地址空間映射到處理器支持的物理地址空間中爽待。

地址空間的不同視圖

所以說,我們所看到的只是邏輯地址翩腐,我們在代碼中對所謂內(nèi)存的操作鸟款,也只是對邏輯地址的操作,這些操作的具體過程是由編譯器和操作系統(tǒng)以及硬件為我們完成的茂卦。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末何什,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子等龙,更是在濱河造成了極大的恐慌富俄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件而咆,死亡現(xiàn)場離奇詭異霍比,居然都是意外死亡,警方通過查閱死者的電腦和手機暴备,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門悠瞬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涯捻,你說我怎么就攤上這事浅妆。” “怎么了障癌?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵凌外,是天一觀的道長。 經(jīng)常有香客問我涛浙,道長康辑,這世上最難降的妖魔是什么摄欲? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮疮薇,結果婚禮上胸墙,老公的妹妹穿的比我還像新娘。我一直安慰自己按咒,他們只是感情好迟隅,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著励七,像睡著了一般智袭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掠抬,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天俗孝,我揣著相機與錄音镊讼,去河邊找鬼绍昂。 笑死蟀给,一個胖子當著我的面吹牛撩匕,可吹牛的內(nèi)容都是我干的锦亦。 我是一名探鬼主播宇弛,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼曙痘,長吁一口氣:“原來是場噩夢啊……” “哼阳准!你這毒婦竟也來了氛堕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤野蝇,失蹤者是張志新(化名)和其女友劉穎讼稚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绕沈,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡锐想,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乍狐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赠摇。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浅蚪,靈堂內(nèi)的尸體忽然破棺而出藕帜,到底是詐尸還是另有隱情,我是刑警寧澤惜傲,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布洽故,位于F島的核電站,受9級特大地震影響盗誊,放射性物質(zhì)發(fā)生泄漏时甚。R本人自食惡果不足惜隘弊,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撞秋。 院中可真熱鬧长捧,春花似錦、人聲如沸吻贿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舅列。三九已至肌割,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帐要,已是汗流浹背把敞。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榨惠,地道東北人奋早。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像赠橙,于是被迫代替她去往敵國和親耽装。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容