標籤:gnu 規劃 tab exe 原因 cad 機器 回收 也會
之前在學Java的時候對於Java虛擬機器中的記憶體分布有一定的瞭解,但是最近在看一些C,發現居然自己對於C語言的記憶體配置瞭解的太少。
問題不能拖,我這就來學習一下吧,爭取一次搞定。 在任何程式設計環境及語言中,記憶體管理都十分重要。
這篇學習部落客要分為三個部分:
第一部分主要介紹記憶體管理基本概念,重點介紹C程式中記憶體的分配,以及C語言編譯後的可執行程式的儲存結構和運行結構,同時還介紹了堆空間和棧空間的用途及區別。
第二部分主要介紹C語言中記憶體配置及釋放函數、函數的功能,以及如何調用這些函數申請/釋放記憶體空間及其注意事項。
第三部分就是各種奇淫巧技了,各種之前忽略情況。
第一部分:記憶體管理的基本概念
分析C語言記憶體的分布先從Linux下可執行檔C程式入手。現在有一個簡單的C來源程式hello.c
1 #include <stdio.h>2 #include <stdlib.h>3 int var1 = 1;4 5 int main(void) {6 int var2 = 2;7 printf("hello, world!\n");8 exit(0);9 }
經過gcc hello.c進行編譯之後得到了名為a.out的可執行檔
[[email protected] leet_code]$ ls -al a.out
-rwxrwxr-x. 1 tuhooo tuhooo 8592 Jul 22 20:40 a.out
ls命令是查看檔案的中繼資料資訊
[[email protected] leet_code]$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=23c58f2cad39d8b15b91f0cc8129055833372afe, not stripped
file命令用來識別檔案類型,也可用來辨別一些檔案的編碼格式。
它是通過查看檔案的頭部資訊來擷取檔案類型,而不是像Windows通過副檔名來確定檔案類型的。
[[email protected] leet_code]$ size a.out
| text |
data |
bss |
dec |
hex |
filename |
| (代碼區待用資料) |
(全域初始化待用資料) |
(未初始化資料區) |
(十進位總和) |
(十六制總和) |
(檔案名稱) |
| 1301 |
560 |
8 |
1869 |
74d |
a.out |
顯示一個目標檔案或者連結庫檔案中的目標檔案的各個段的大小,當沒有輸入檔案名稱時,預設為a.out。
size:支援的目標: elf32-i386 a.out-i386-linux efi-app-ia32 elf32-little elf32-big srec symbolsrec tekhex binary ihex trad-core。
那啥,可執行檔在儲存(也就是還沒有載入到記憶體中)的時候,分為:代碼區、資料區和未初始化資料區3個部分。
進一步解讀
(1)代碼區(text segment)。存放CPU執行的機器指令(machine instructions)。通常,代碼區是可共用的(即另外的執行程式可以調用它),因為對於頻繁被執行的程式,只需要在記憶體中有一份代碼即可。代碼區通常是唯讀,使其唯讀原因是防止程式意外地修改了它的指令。另外,代碼區還規划了局部變數的相關資訊。
(2)全域初始化資料區/待用資料區(initialized data segment/data segment)。該區包含了在程式中明確被初始化的全域變數、靜態變數(包括全域靜態變數和局部靜態變數)和常量資料(如字串常量)。例如,一個不在任何函數內的聲明(全域資料):
1 int maxcount = 99;
使得變數maxcount根據其初始值被儲存到初始化資料區中。
1 static mincount = 100;
這聲明了一個待用資料,如果是在任何函數體外聲明,則表示其為一個全域靜態變數,如果在函數體內(局部),則表示其為一個局部靜態變數。另外,如果在函數名前加上static,則表示此函數只能在當前檔案中被調用。
(3)未初始化資料區。亦稱BSS區(uninitialized data segment),存入的是全域未初始設定變數。BSS這個叫法是根據一個早期的彙編運算子而來,這個彙編運算子標誌著一個塊的開始。BSS區的資料在程式開始執行之前被核心初始化為0或者null 指標(NULL)。例如一個不在任何函數內的聲明:
1 long sum[1000];
將變數sum儲存到未初始化資料區。
所示為可執行代碼儲存時結構和運行時結構的對照圖。一個正在運行著的C編譯器佔用的記憶體分為代碼區、初始化資料區、未初始化資料區、堆區和棧區5個部分。
(1)代碼區(text segment)。代碼區指令根據程式設計流程依次執行,對於順序指令,則只會執行一次(每個進程),如果反覆,則需要使用跳轉指令,如果進行遞迴,則需要藉助棧來實現。程式碼片段: 程式碼片段(code segment/text segment )通常是指用來存放程式執行代碼的一塊記憶體地區。這部分地區的大小在程式運行前就已經確定,並且記憶體地區通常屬於唯讀, 某些架構也允許程式碼片段為可寫,即允許修改程式。在程式碼片段中,也有可能包含一些唯讀常數變數,例如字串常量等。代碼區的指令中包括作業碼和要操作的對象(或對象地址引用)。如果是立即數(即具體的數值,如5),將直接包含在代碼中;如果是局部資料,將在棧區分配空間,然後引用該資料地址;如果是BSS區和資料區,在代碼中同樣將引用該資料地址。
(2)全域初始化資料區/待用資料區(Data Segment)。只初始化一次。資料區段: 資料區段(data segment )通常是指用來存放程式中已初始化的全域變數的一塊記憶體地區。資料區段屬於靜態記憶體配置。
(3)未初始化資料區(BSS)。在運行時改變其值。BSS 段: BSS 段(bss segment )通常是指用來存放程式中未初始化的全域變數的一塊記憶體地區。BSS 是英文Block Started by Symbol 的簡稱。BSS 段屬於靜態記憶體配置。
(4)棧區(stack)。由編譯器自動分配釋放,存放函數的參數值、局部變數的值等。其操作方式類似於資料結構中的棧。每當一個函數被調用,該函數返回地址和一些關於調用的資訊,比如某些寄存器的內容,被儲存到棧區。然後這個被調用的函數再為它的自動變數和臨時變數在棧區上分配空間,這就是C實現函數遞迴調用的方法。每執行一次遞迴函式調用,一個新的棧架構就會被使用,這樣這個新執行個體棧裡的變數就不會和該函數的另一個執行個體棧裡面的變數混淆。棧(stack) :棧又稱堆棧, 是使用者存放程式臨時建立的局部變數,也就是說我們函數括弧"{}"中定義的變數(但不包括static 聲明的變數,static 意味著在資料區段中存放變數)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,並且待到調用結束後,函數的傳回值也會被存放回棧中。由於棧的先進先出特點,所以棧特別方便用來儲存/ 恢複調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時資料的記憶體區。
(5)堆區(heap)。用於動態記憶體分配。堆在記憶體中位於bss區和棧區之間。一般由程式員分配和釋放,若程式員不釋放,程式結束時有可能由OS回收。堆(heap): 堆是用於存放進程運行中被動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。當進程調用malloc 等函數分配記憶體時,新分配的記憶體就被動態添加到堆上(堆被擴張);當利用free 等函數釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)。
為什麼要這麼分配記憶體?
待續。。。。。。
深入理解C語言記憶體管理