**:
在任何程式設計環境及語言中,記憶體管理都十分重要。在目前的計算機系統或嵌入式系統中,記憶體資源仍然是有限的。因此在程式設計中,有效地管理記憶體資源是程式設計師首先考慮的問題。
第1節主要介紹記憶體管理基本概念,重點介紹c程式中記憶體的分配,以及c語言編譯後的可執行程式的儲存結構和執行結構,同時還介紹了堆空間和棧空間的用途及區別。
第2節主要介紹c語言中記憶體分配及釋放函式、函式的功能,以及如何呼叫這些函式申請/釋放記憶體空間及其注意事項。
第3節主要介紹了在linux下除gdb之外的記憶體除錯及管理工具,包括glibc提供的記憶體管理函式、memwatch記憶體錯誤檢測工具和valgrind記憶體檢測工具。重點介紹使用這三類工具進行記憶體檢查的原理及相關示例。
3.1 記憶體管理基本概念
3.1.1 c程式記憶體分配
1.c程式結構
下面列出c語言可執行程式的基本情況(linux 2.6環境/gcc4.0)。[root@localhost ctest]# ls test -l //test為乙個可執行程式
-rwxr-xr-x 1 root root 4868 mar 26 08:10 test
[root@localhost ctest]# file test //此檔案基本情況
test: elf 32-bit lsb executable, intel 80386, version 1 (sysv),
for gnu/linux 2.2.5, dynamically linked (uses shared libs), not stripped
[root@localhost ctest]# size test //此二進位制可執行檔案結構情況
//**區靜態資料/全域性初始化資料區 未初始化資料區 十進位制總和 十六進製制總和 檔名
text data bss dec hex filename
906 284 4 1194 4aa test
可以看出,此可執行程式在儲存時(沒有調入到記憶體)分為**區(text)、資料區(data)和未初始化資料區(bss)3個部分。
(1)**區(text segment)。存放cpu執行的機器指令(machine instructions)。通常,**區是可共享的(即另外的執行程式可以呼叫它),因為對於頻繁被執行的程式,只需要在記憶體中有乙份**即可。**區通常是唯讀的,使其唯讀的原因是防止程式意外地修改了它的指令。另外,**區還規劃了區域性變數的相關資訊。
(2)全域性初始化資料區/靜態資料區(initialized data segment/data segment)。該區包含了在程式中明確被初始化的全域性變數、靜態變數(包括全域性靜態變數和區域性靜態變數)和常量資料(如字串常量)。例如,乙個不在任何函式內的宣告(全域性資料):int maxcount = 99;
使得變數maxcount根據其初始值被儲存到初始化資料區中。static mincount=100;
這宣告了乙個靜態資料,如果是在任何函式體外宣告,則表示其為乙個全域性靜態變數,如果在函式體內(區域性),則表示其為乙個區域性靜態變數。另外,如果在函式名前加上static,則表示此函式只能在當前檔案中被呼叫。
(3)未初始化資料區。亦稱bss區(uninitialized data segment),存入的是全域性未初始化變數。bss這個叫法是根據乙個早期的彙編運算子而來,這個彙編運算子標誌著乙個塊的開始。bss區的資料在程式開始執行之前被核心初始化為0或者空指標(null)。例如乙個不在任何函式內的宣告:long sum[1000];
將變數sum儲存到未初始化資料區。
圖3-1所示為可執行**儲存時結構和執行時結構的對照圖。乙個正在執行著的c編譯程式占用的記憶體分為**區、初始化資料區、未初始化資料區、堆區和棧區5個部分。
(1)**區(text segment)。**區指令根據程式設計流程依次執行,對於順序指令,則只會執行一次(每個程序),如果反覆,則需要使用跳轉指令,如果進行遞迴,則需要借助棧來實現。
**區的指令中包括操作碼和要操作的物件(或物件位址引用)。如果是立即數(即具體的數值,如5),將直接包含在**中;如果是區域性資料,將在棧區分配空間,然後引用該資料位址;如果是bss區和資料區,在**中同樣將引用該資料位址。
(2)全域性初始化資料區/靜態資料區(data segment)。只初始化一次。
(3)未初始化資料區(bss)。在執行時改變其值。
(4)棧區(stack)。由編譯器自動分配釋放,存放函式的引數值、區域性變數的值等。其操作方式類似於資料結構中的棧。每當乙個函式被呼叫,該函式返回位址和一些關於呼叫的資訊,比如某些暫存器的內容,被儲存到棧區。然後這個被呼叫的函式再為它的自動變數和臨時變數在棧區上分配空間,這就是c實現函式遞迴呼叫的方法。每執行一次遞迴函式呼叫,乙個新的棧框架就會被使用,這樣這個新例項棧裡的變數就不會和該函式的另乙個例項棧裡面的變數混淆。
(5)堆區(heap)。用於動態記憶體分配。堆在記憶體中位於bss區和棧區之間。一般由程式設計師分配和釋放,若程式設計師不釋放,程式結束時有可能由os**。
之所以分成這麼多個區域,主要基於以下考慮:
乙個程序在執行過程中,**是根據流程依次執行的,只需要訪問一次,當然跳轉和遞迴有可能使**執行多次,而資料一般都需要訪問多次,因此單獨開闢空間以方便訪問和節約空間。
臨時資料及需要再次使用的**在執行時放入棧區中,生命周期短。
全域性資料和靜態資料有可能在整個程式執行過程中都需要訪問,因此單獨儲存管理。
堆區由使用者自由分配,以便管理。
下面通過一段簡單的**來檢視c程式執行時的記憶體分配情況。相關資料在執行時的位置如注釋所述。
int a = 0; //a在全域性已初始化資料區
char *p1; //p1在bss區(未初始化全域性變數)
main()
2.記憶體分配方式
在c語言中,物件可以使用靜態或動態的方式分配記憶體空間。
靜態分配:編譯器在處理程式源**時分配。
動態分配:程式在執行時呼叫malloc庫函式申請分配。
靜態記憶體分配是在程式執行之前進行的因而效率比較高,而動態記憶體分配則可以靈活的處理未知數目的。
靜態與動態記憶體分配的主要區別如下:
靜態物件是有名字的變數,可以直接對其進行操作;動態物件是沒有名字的變數,需要通過指標間接地對它進行操作。
靜態物件的分配與釋放由編譯器自動處理;動態物件的分配與釋放必須由程式設計師顯式地管理,它通過malloc()和free兩個函式(c++中為new和delete運算子)來完成。
以下是採用靜態分配方式的例子。int a=100;
此行**指示編譯器分配足夠的儲存區以存放乙個整型值,該儲存區與名字a相關聯,並用數值100初始化該儲存區。
以下是採用動態分配方式的例子。p1 = (char *)malloc(10*sizeof(int));//分配得來得10*4位元組的區域在堆區
此行**分配了10個int型別的物件,然後返回物件在記憶體中的位址,接著這個位址被用來初始化指標物件p1,對於動態分配的記憶體唯一的訪問方式是通過指標間接地訪問,其釋放方法為:free(p1);
c 程式記憶體分配
乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os 注意它與資料結構中的堆是兩回事,分配方式倒是類...
C程式記憶體分配
從作業系統的角度簡單介紹一下程序。程序是占有資源的最小單位,這個資源當然包括記憶體。在現代作業系統中,每個程序所能訪問的記憶體是互相獨立的 一些交換區除外 而程序中的執行緒所以共享程序所分配的記憶體空間。在作業系統的角度來看,程序 程式 資料 pcb 程序控制塊 區 text 用來存放cpu執行的機...
C程式的記憶體分配
c語言有五個區 1.stack 用來存放函式的形參和函式內的區域性變數。由編譯器分配空間,在函式執行完後由編譯器自動釋放。2.heap 用來存放由動態分配函式 如malloc new 分配的空間。是由程式設計師自己手動分配的,並且必須由程式設計師使用free釋放。如果忘記用free釋放,會導致所分配...