進程內存佈局

一. 概述

在長期計算機科學的發展中,程序在內存分區已經形成固定的佈局。無論是在 Linux/Unix 下的應用程序,還是 Windows 下的程序,甚至是在沒有操作系統下的底層程序,如 bootloader,以及 ARM 中單純的 C 程序,它們都按大致固定的來進行佈局。其他的開發語言,如 Java 也採用同樣的佈局方式。

二. 保存時的分段(可執行程序段的分配)

  • 代碼段(Text Segment):存放 CPU 執行的機器指令(machine instructions),也就是程序代碼編譯後的機器代碼。在內存中,這一段是隻讀的。所有可執行代碼都會被編譯到可執行段中。
  • 初始化數據段/數據段(Initialized Data Segment/Data Segment):存放靜態初始化的數據,即有初始值的全局變量和 static 變量。數據將在此段中展開。
  • 未初始化數據段/bss段(Block Started by Symbol):存放未初始化的全局變量和 static 變量。在程序載入時由內核清0,所有未初始化的全局變量(包括靜態&非靜態)均保存在這個段中,實際上保存的是變量的標識符和大小。

注意:在可執行程序段中沒有局部變量的空間,因爲它們要在程序執行時,在進程的空間中的 Stack 區動態地被創建。

三. 分配實例

#include <stdio.h>
int a; // 未初始化,分配到bss段
static int b = 1; // 已初始化,分配到data段
int c = 2; // 已初始化,分配到data段
static char d[10] = { 'a', 'b' }; // 已初始化,分配到data段,其中數據直接寫在data空間

int main() {
	int e = 3; // 局部變量,在程序文件裏沒有位置
	printf("%d, %d, %d, %s, %d", a, b, c, d, e); // Output: 0, 1, 2, ab, 3
	return 0;
}

四. 運行後的內存分區

當程序運行後,除了上述段會裝入進程空間,還有兩個新的區會被分配到其中。

  • 棧段(stack):保存函數的局部變量和函數參數&返回值
    • LIFO(Last In First Out),最後放到棧上的數據將會被第一個移走。
  • 堆段(heap):保存函數內部動態分配的內存,是另外一種用來保存程序信息的數據結構,更準確地說是保存程序的動態變量。
    • FIFO(First In First Out),只允許在一端插入數據,在另一端移走數據。
    • 所有 malloc 分配的內存空間都是從堆中進行分配,用 free 把不需要的空間還給堆。
    • 堆的地址空間“向上增加”,即當堆上保存的數據越多,堆的地址就越高。

在一個應用程序的虛擬空間中,堆的大小遠大於棧,而棧被頻繁地使用。棧比一般開發者想象得還要小,有時會小到只有4096字節。因此在一個應用程序中分配一個巨大的自動變量和分配一個全部變量一樣,都是一件不明智的事,在運行時會立刻造成程序段的錯誤。比較好的做法是用動態分配內存(dynamic memory allocation)的方法來分配巨大的結構。

int main() {
	/* 採用棧分配,在某些OS中可能會造成段錯誤 */
	// char buf[25000]; 
	/* 應該採用堆來分配巨大的變量 */
	char buf = malloc(25000);
	...
	free(buf);
}	

五. 進程典型分區

代碼段(Text Segment)從低地址端開始往上,棧底從高地址端開始往下。在堆頂和棧頂之間的虛擬地址空間很大,保證兩個段不會相互干擾。

在這裏插入圖片描述
Linux進程內存佈局:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章