再談c的記憶體管理及指標問題

2021-07-03 23:52:58 字數 2851 閱讀 7623

記憶體空間主要由五個部分組成**段(.text)、資料段(.data)、bss段(.bss),堆和棧組成,其中**段,資料段和bss段是編譯的時候由編譯器分配的,而堆和 棧是程式執行的時候由系統分配的。布局如下:

下面分別解釋各段:

bss段:用來存放程式中未初始化的全域性變數和靜態變數(初始化分為顯式和隱式初始化,未初始化指程式設計師不初始化的話,自動初始化為0。)不佔磁碟空間,只在執行在再用記憶體空間間。

資料段:資料段(data segment)通常是指用來存放程式中已初始化的全域性變數和靜態變數的一塊記憶體區域。資料段屬於靜態記憶體分配,可以分為唯讀資料段和讀寫資料段。 字串常量等,但一般都是放在唯讀資料段中。

**段:**段(code segment/text segment)通常是指用來存放程式執行**的一塊記憶體區域。這部分區域的大小在程式執行前就已經確定,並且記憶體區域通常屬於唯讀, 某些架構也允許**段為可寫,即允許修改程式。在**段中,也有可能包含一些唯讀的常數變數,例如字串常量等,但一般都是放在唯讀資料段中 。

堆(heap):堆是用於存放程序執行中被動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。當程序呼叫malloc等函式分配記憶體時,新分配的記憶體就被動態新增到堆上(堆被擴張);當利用free等函式釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減) 。

棧 (stack):棧又稱堆疊, 是使用者存放程式臨時建立的區域性變數,也就是說我們函式括弧「{}」中定義的變數(但不包括static宣告的變數,static意味著在資料段中存放變 量)。除此以外,在函式被呼叫時,其引數也會被壓入發起呼叫的程序棧中,並且待到呼叫結束後,函式的返回值也會被存放回棧中。由於棧的先進後出特點,所以 棧特別方便用來儲存/恢復呼叫現場。從這個意義上講,我們可以把堆疊看成乙個寄存、交換臨時資料的記憶體區。注意:棧空間是向下增長的,每個執行緒有乙個自己的棧,在linux上預設的大小是8m,可以用ulimit檢視和修改。

靜態儲存區域分配:內存在程式編譯的時候已經分配好,這塊內存在程式的整個執行空間都存在(全域性變數,static變數)

在棧上建立:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。

從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。

1. 記憶體分配未成功,卻使用了它

常用解決辦法,如果指標p是函式的引數,進行assert(p!==null)檢查。如果是malloc和new來申請記憶體,應該用if(p==null)等進行防錯處理。

2. 記憶體分配雖然成功,但是尚未初始化就引用它

犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值錯誤(例如陣列)。記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。

3. 記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。

這種錯誤常發生在陣列和for語句中。

4. 忘記了釋放記憶體,造成記憶體洩露。

動態記憶體的申請和釋放一定要配對。

五個原則

【規則1】用malloc或new申請記憶體之後,應該立即檢查指標值是否為null。防止使用指標值為null的記憶體。

【規則2】不要忘記為陣列和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。

【規則3】避免陣列或指標的下標越界,特別要當心發生「多1」或者「少1」操作。

【規則4】動態記憶體的申請與釋放必須配對,防止記憶體洩漏。

【規則5】用free或delete釋放了記憶體之後,立即將指標設定為null,防止產生「野指標」。

在c/c++程式中,常產生一種錯覺,指標和陣列可以相互替換著用。

1.陣列要麼在靜態儲存區域被建立(如全域性陣列),要麼在棧上建立。

陣列名對應著(而不是指向)一塊記憶體,其位址和容量在生命週期內不變,陣列內容可以變。

2.指標可以隨時指向任意型別的記憶體塊,「可變」

例項程式:

void test{

char a = "hello";

a[0] = 'x';

cout

<< a << endl; //xello

char *p = "world";

cout

<< p << endl; //world

//p[0] = 'x';

return

0; }

註解:字元陣列a在棧上建立,內容為hello,且可以改變。

指標p指向常量字串world(位於靜態儲存區)。常量字串的內容是不可以被修改的。因此p[0] = 『x』雖然在語法上正確,但執行時程式異常退出。

運算子sizeof可以計算乙個變數的容量(位元組數)。

void test()

註解:a是乙個陣列,其大小為12. 而p是乙個指標,因此得到乙個指標變數的位元組數。 c /c語言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。

*當陣列用作函式引數時,該陣列自動退化為同型別的指標。

指標 陣列及記憶體管理

我們先來了解一下c與c 的記憶體管理。常見的記憶體錯誤及其對策 發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程式執行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時使用者怒氣沖沖地把你找來,程式卻沒有發生任何問題,你一走,錯誤又發作了。常見的記憶...

C 程式設計之記憶體管理(再談)

在 常見的記憶體錯誤及其對策 c 中,記憶體分成 5 個區,他們分別是堆 棧 自由儲存區 全域性 靜態儲存區和常量儲存區。管理方式 對於棧來講,是由編譯器自動管理,無需我們手工控制 對於堆來說,釋放工作由程式設計師控制,容易產生 memory leak。空間大小 一般來講在 32 位系統下,堆記憶體...

c 記憶體管理 指標的用法

指標是記憶體能管理的重要組成部分。new和delete運算子提供了一種比自動變數和靜態變數更靈活的方法。它們管理了乙個記憶體池,這在c 中被稱為自由儲存空間 free store 或堆 heap 該記憶體池同用於靜態變數和自動變數的記憶體是分開的。new和delete讓您能夠在乙個函式中分配記憶體,...