程序可執(zhí)行文件的結(jié)構(gòu)一個(gè)程序的可執(zhí)行文件在內(nèi)存中的結(jié)果,從大的角度可以分為兩個(gè)部分:只讀部分和可讀寫部分。只讀部分包括程序代碼(.text)和程序中的常量(.rodata)。可讀寫部分(也就是變量)大致可以分成下面幾個(gè)部分:
下面就各個(gè)分區(qū)具體解釋一下: data 和 bss 區(qū) 這兩個(gè)區(qū)經(jīng)常放在一起說,因?yàn)樗麄兌际怯脕泶鎯?chǔ)全局變量和靜態(tài)變量的,區(qū)別在于 data 區(qū)存放的是初始化過的, bss 區(qū)存放的是沒有初始化過的,例如: int val = 3;char string[] = 'Hello World'; 這兩個(gè)變量的值會(huì)一開始被存儲(chǔ)在 .text 中(因?yàn)橹凳菍懺诖a里面的),在程序啟動(dòng)時(shí)會(huì)拷貝到 .data 去區(qū)中。 而不初始化的話,像下面這樣: static int i; 這個(gè)變量就會(huì)被放在 bss 區(qū)中。 答疑一 靜態(tài)變量和全局變量 這兩個(gè)概念都是很常見的概念,又經(jīng)常在一起使用,很容易造成混淆。
a.c #include b.c int a;int compute(void){ a = 0; return a;} 在 Link 過程中會(huì)產(chǎn)生重復(fù)定義錯(cuò)誤,因?yàn)橛袃蓚€(gè)全局的 a 變量,Linker 不知道應(yīng)該使用哪一個(gè)。為了避免這種情況,就需要引入 static。
對(duì)于全局變量來說,為了避免上面提到的重復(fù)定義錯(cuò)誤,我們可以在一個(gè)文件中使用 static,另一個(gè)不使用。這樣使用 static 的就會(huì)使用自己的 a 變量,而沒有用 static 的會(huì)使用全局的 a 變量。當(dāng)然,最好兩個(gè)都使用 static,避免更多可能的命名沖突。 注意:'靜態(tài)'這個(gè)中文翻譯實(shí)在是有些莫名其妙,給人的感覺像是不可改變的,而實(shí)際上 static 跟不可改變沒有關(guān)系,不可改變的變量使用 const 關(guān)鍵字修飾,注意不要混淆。 Bonus 部分 —— extern: extern 是 C 語言中另一個(gè)關(guān)鍵字,用來指示變量或函數(shù)的定義在別的文件中,使用 extern 可以在多個(gè)源文件中共享某個(gè)變量,例如這里的例子。 extern 跟 static 在含義上是“水火不容”的,一個(gè)表示不能在別的地方用,一個(gè)表示要去別的地方找。如果同時(shí)使用的話,有兩種情況,一種是先使用 static,后使用 extern ,即: static int m;extern int m; 這種情況,后面的 m 實(shí)際上就是前面的 m 。如果反過來: extern int m;static int m; 這種情況的行為是未定義的,編譯器也會(huì)給出警告。 答疑二 程序在內(nèi)存和硬盤上不同的存在形式 這里我們提到的幾個(gè)區(qū),是指程序在內(nèi)存中的存在形式。和程序在硬盤上存儲(chǔ)的格式不是完全對(duì)應(yīng)的。程序在硬盤上存儲(chǔ)的格式更加復(fù)雜,而且是和操作系統(tǒng)有關(guān)的,具體可以參考這里。一個(gè)比較明顯的例子可以幫你區(qū)分這個(gè)差別:之前我們提到過未定義的全局變量存儲(chǔ)在 棧棧是用于存放本地變量,內(nèi)部臨時(shí)變量以及有關(guān)上下文的內(nèi)存區(qū)域。程序在調(diào)用函數(shù)時(shí),操作系統(tǒng)會(huì)自動(dòng)通過壓棧和彈棧完成保存函數(shù)現(xiàn)場(chǎng)等操作,不需要程序員手動(dòng)干預(yù)。 棧是一塊連續(xù)的內(nèi)存區(qū)域,棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的。能從棧獲得的空間較小。如果申請(qǐng)的空間超過棧的剩余空間時(shí),例如遞歸深度過深,將提示stackoverflow。 棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計(jì)算機(jī)會(huì)在底層對(duì)棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高。 堆堆是用于存放除了棧里的東西之外所有其他東西的內(nèi)存區(qū)域,當(dāng)使用 堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。 對(duì)于堆來講,頻繁的new/delete勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。對(duì)于棧來講,則不會(huì)存在這個(gè)問題,因?yàn)闂J窍冗M(jìn)后出的隊(duì)列,永遠(yuǎn)都不可能有一個(gè)內(nèi)存塊從棧中間彈出。 堆都是動(dòng)態(tà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)行釋放,無需我們手工實(shí)現(xiàn)。 計(jì)算機(jī)底層并沒有對(duì)堆的支持,堆則是C/C++函數(shù)庫提供的,同時(shí)由于上面提到的碎片問題,都會(huì)導(dǎo)致堆的效率比棧要低。 內(nèi)存分配
虛擬地址先經(jīng)過分段機(jī)制映射到線性地址,然后線性地址通過分頁機(jī)制映射到物理地址。 虛擬內(nèi)存
頁面置換算法
內(nèi)存抖動(dòng)現(xiàn)象:頁面的頻繁更換,導(dǎo)致整個(gè)系統(tǒng)效率急劇下降,這個(gè)現(xiàn)象稱為內(nèi)存抖動(dòng)(或顛簸)。抖動(dòng)一般是內(nèi)存分配算法不好,內(nèi)存太小引或者程序的算法不佳引起的。 Belady現(xiàn)象:對(duì)有的頁面置換算法,頁錯(cuò)誤率可能會(huì)隨著分配幀數(shù)增加而增加。 FIFO會(huì)產(chǎn)生Belady異常。 棧式算法無Belady異常,LRU,LFU(最不經(jīng)常使用),OPT都屬于棧式算法。 |
|