天天看點

【C語言進階剖析】40、程式的記憶體布局

1 什麼是程式?

2 程式與程序

3 程式的記憶體布局

4 程式術語的對應關系

5 小結

寫完的 .c 檔案是源檔案。也叫源代碼。

将源代碼編譯後,會生成可執行檔案程式(linux下是.out,windows下是.exe)。這個檔案就是程式。也叫作可執行代碼。

源代碼與可執行檔案的對應如下,也就是程式檔案的布局:

【C語言進階剖析】40、程式的記憶體布局

可以看到,程式被編譯器編譯過後:

初始化的全變量和靜态局部變量在 .data 段,未初始化的全局變量和靜态局部變量在 .bss 段,函數等代碼在 .text 段。

局部非靜态變量存儲在棧中,在右側的圖中沒有對應,因為堆和棧是在程式運作開始後才正式存在,可執行程式中是沒有棧、堆這樣的存儲區的。

file header 是一個檔案頭,作業系統根據這個檔案頭就能判斷這是一個什麼樣的可執行程式,那也就知道了怎麼樣來運作這個程式。

在上一篇部落格中講到全局變量和靜态變量位于靜态存儲區,那也就是說 .data 段和 .bss 對應着靜态存儲區。

前面一直說可執行程式,那麼可執行程式在運作前和運作後有什麼差異呢?,下面來看看程式與程序。

程式與程序不同

程式是靜态的概念,表現形式為一個可執行檔案

程序是動态的概念。程式由作業系統加載運作後得到的程序

每個程式可以對應多個程序,每個程序隻能對應一個程式

問題:包含腳本代碼的文本檔案是一種可行性程式嗎?如果是,對應什麼樣的程序呢?

腳本檔案是一個文本檔案,我們經常會說運作某個腳本檔案。其實腳本代碼是一種可執行程式,但不是一種直接的可執行程式,既然是可執行程式一定對應程序,對應的是什麼程序呢,下面來說:

【C語言進階剖析】40、程式的記憶體布局

一個可執行程式被作業系統加載運作後就得到了一個程序。對于 window 系統,輕按兩下一個 .exe 檔案,作業系統加載這個檔案,就得到一個程序。對于 linux 通過指令行加載,和 window 原理一樣。這個程序的功能是确定的。

對于腳本檔案,作業系統首先檢視腳本檔案對應的腳本解釋程式,然後加載腳本解釋程式,就得到了程序,這個程序會反過來讀取腳本檔案,這個程序的功能是不确定的,取決于它讀取并解釋的執行的腳本檔案。腳本檔案不能直接被作業系統加載,需要加載腳本解釋程式得到程序,這樣一個腳本檔案最終還是對應了一個程序。

可執行程式和加載後程序的布局如下:

【C語言進階剖析】40、程式的記憶體布局
file header 是告訴作業系統如何運作這個程式,是以加載後程序中就沒有 file header 了。

各個段的作用:

堆棧段在程式運作後才正是存在,是程式運作的基礎

.bss 段存在的是未初始化的全局變量和靜态變量,以及所有被初始化為 0 的全局或靜态變量,在目标檔案中,這個節不占據實際的空間,他僅僅是一個占位符,這是為了空間效率。

.data 段儲存的是已經初始化的全局變量個靜态變量

.text 段存放的是程式中的可執行代碼

.rodata 段存放程式中的常量值,如字元串常量

靜态存儲區通常指程式中的 .bss 和 .data 段

隻讀存儲區通常指程式中的 .rodata 段

局部變量所占空間為棧上空間

動态空間為堆中的空間

程式可執行代碼存放于 .text 段

問題:同時全局變量和靜态變量,為什麼初始化和未初始化的儲存在不同段中?

c 規定,未初始化變量的初值為 0,這個清 0 的操作是由啟動代碼完成的,還有已初始化變量的初值的設定,也是由啟動代碼完成的。

為了啟動代碼的簡單化,編譯連結器會把已初始化的變量放在同一個段:.data,這個段的映像(包含了各個變量的初值)儲存在“隻讀資料段”,這樣啟動代碼就可以簡單地複制這個映像到 .data 段,所有的已初始化變量就都初始化了。

而未初始化變量也放在同一個段:.bss,啟動代碼簡單地調用 memset 就可以把所有未初始化變量都清0。

1、程式源碼在編譯後對應可執行程式中的不同存儲區

2、程式是靜态概念,程序是動态概念

3、堆棧段是程式運作的基礎,隻存在于程序空間中

4、程式可執行代碼存放于 .text 段,是隻讀的

5、.bss 和 .data 段用于儲存全局變量個靜态變量