linux--程序在記憶體中的佈局
先從(Linux平臺下)虛擬記憶體管理說起,
寫C程式時,我們經常會列印一個指標地址,說這個指標指向某某記憶體地址.可這些地址是真實實體記憶體地址嗎?不是!這些只是虛擬記憶體地址.
當一個C程式調入記憶體開始執行後,在記憶體中就會產生一個程序.而在多工作業系統中每個程序都擁有一片屬於自己的記憶體空間(記憶體沙盤),這個沙盤就是虛擬地址空間,在32位下是一個4GB的大小的地址塊,這些虛擬地址通過頁表對映到實體記憶體.
但系統並不會真的一下分配給每一個程序4GB的實體記憶體空間的對映= =(不現實啊),這4GB只能是說邏輯地址,它會隨著程序的真實需要自動擴充套件對映到實體記憶體空間,最大到4GB.
4GB(地址0-0xFFFFFFFF)其中1GB必須保留給系統核心(這是Linux平臺下),也就是說程序自身只能擁有3GB的地址(0-0xC0000000),如圖
我自己畫的= =:
程式碼區:程式(函式)程式碼所在,由編譯而得到的二進位制程式碼被載入至此.程式碼區是隻讀的!有執行許可權.程式碼區一般都從0x08048000地址開始(linux下).值得注意的是,字串字面值(如"Hello World")就儲存在這個區.
資料段和BSS段:合稱靜態區(全域性區),用來儲存靜態(全域性)變數.區別是 前者(資料段)儲存的是已初始化的靜態(全域性)變數,可讀寫.
後者(BSS段)儲存的是未初始化的靜態(全域性)變數,可讀寫.
堆:自由儲存區.不像全域性變數和區域性變數的生命週期被嚴格定義,堆區的記憶體分配和釋放是由程式設計師所控制的.申請方式:C中是malloc函式,C++中是new識別符號.
棧:由系統自動分配和釋放.儲存區域性(自動)變數. 一般說的堆疊,其實是指 棧!
另外,值得注意的是,堆是由低地址向高地址分配空間;棧卻是由高地址向低地址分配空間.
下面這段程式碼進一步說明C程式中各資料的記憶體佈局:
#include <stdio.h> #include <stdlib.h> int i1 = 10; //靜態全域性區(data段) int i2; //靜態全域性區(bss段) static int i3 = 30; //靜態全域性區(data段) const int i4 = 40; //程式碼區!!! void fun(int i5) //棧區 { int i6 = 60; //棧區 static int i7 = 70; //靜態全域性區(data段) const int i8 = 80; //棧區!!! char* str1 = "ABCDE"; //程式碼區(字串常量) char str2[] = "ABCDE"; //棧區(字元陣列) int* pi = malloc(sizeof(int)); //堆區 printf("&i5=%p\n", &i5); printf("&i6=%p\n", &i6); printf("&i7=%p\n", &i7); printf("&i8=%p\n", &i8); printf("str1=%p\n", str1); printf("str2=%p\n", str2); printf("pi=%p\n", pi); free(pi); } int main(void) { printf("&i1=%p\n", &i1); printf("&i2=%p\n", &i2); printf("&i3=%p\n", &i3); printf("&i4=%p\n", &i4); fun(50); return 0; }
(gcc編譯)程式輸出:
至此,從地址大小比較可以看出
1)靜態(static)全域性變數 和 靜態(static)區域性變數 都在 靜態全域性區.
2)全域性常量i4儲存在程式碼區,而區域性常量i8儲存在棧區.
所以最上面的問題是,程式碼區只讀,修改全域性常量會引發執行時段錯誤,而區域性常量是可以成功賦值修改的.
3)字串字面值在程式碼區(所以不可修改),但是字元指標str1在棧區;字元陣列str2在棧區(所以可以修改).
最後要說,上述是Linux下的虛擬記憶體佈局順序,Windows下會有所不同.但是對於C程式中資料應該儲存在哪裡都是一致的.