7 章
記憶體管理
記憶體分配方式有三種:
從靜態儲存區域分配。 內 存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在。例 如全域性變數,
static
變數。
在棧上建立。
在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單 元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
從堆上分配,亦稱動態 記憶體分配。 程式在執行的時候用
malloc
或new
申請任意多少的記憶體,程式設計師自己負責在何時用
free
或delete
釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
7.2常見的記憶體錯誤及其對策
發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程式執行時才能捕捉到。而這 些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時使用者怒氣沖沖地把你找來,程式卻沒有發生任何問題,你一走,錯誤又發作了。
常見的記憶體錯誤及其對策如下:
記憶體分配未成功,卻使用了它。
程式設計新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,在使用記憶體之前檢查指標是否為
null
。如果指標
p是函式的引數,那麼在函式的入口處用
assert(p!=null)
或if(p!=null) 進 行防錯處理。
記憶體分配雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值錯 誤(例如陣列)。
記憶體的預設初值究竟是 什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻 煩。
記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。
例如在使用陣列時經常發生下標「多
1」或者「少
1」的操作。特別是在
for迴圈語句中,迴圈次數很容易搞錯,導致陣列操作越界。
忘記了釋放記憶體,造成記憶體洩露。
動態記憶體的申請與釋放必須配對,程式中
malloc
與free
的使用次數一定要相同,否則肯定有錯 誤(
new/delete
同理)。
釋放了記憶體卻繼續使用它。
有三種情況:
(1)程式中的物件呼叫關係過於複雜,實在難以 搞清楚某個物件究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決物件管理的混亂局面。
(2)函式的
return
語句寫錯了,注意不要返回指向「棧記憶體」的「指標」或者「引用」,因為該內存在函式體結束時被自動銷 毀。 (
3)使用
free
或delete
釋放了記憶體後,沒有將指標設定為
null
。導致產生「野指標」。
【規則
7-2-1】用malloc 或new 申請記憶體之後,應該立即檢查指標值是否為null 。 防止使用指標值為null 的記憶體 。
【規則
7-2-2】不要忘記為陣列和動態記憶體賦初值。
防止 將未被初始化的記憶體作為右值使用 。
【規則
7-2-3】避免陣列或指標的下標越界,特別要當心發生「多
1」或者「少
1」操作。
【規則
7-2-4】動態記憶體的申請與釋放必須配對,防止記憶體洩漏。
【規則
7-2-5】用
free
或delete
釋放了記憶體之後,立即將指標設定為
null
,防止產生「野指標」。
c++/c
程式中,指標和陣列在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。
陣列要麼在靜態儲存區被建立(如全域性陣列 ),要麼在棧上被建立。陣列名對應著(而不是指向) 一塊記憶體,其位址與容 量在生命期內保持不變,只有陣列的內容可以改變。
指標可以隨時指向任意型別的記憶體塊,它的特徵是「可變」,所以我們常用指標來操作動態內 存。指標遠比陣列靈活,但也更危險。
下面以字串為
例比較指標與陣列的特性。
7.3.1
修 改內容
示例7-3-1
中, 字元陣列
a的容量是
6個字元,其內容為
hello/0。a
的 內容可以改變,如
a[0]= 『x』
。指標p指向常量字串「
world
」 (位於靜態儲存區,內容為
world/0
),常量字串的內容是不可以被修改的。從語法上看,編譯 器並不覺得語句
p[0]= 『x』
有什麼不妥,但是該語句企圖修改常量字串的內容而導致執行錯 誤。 「
hello」;
『x』;
cout << a << endl;
「world
」 注意p 指向常量字串 『x
』 編譯器不能發現該錯誤
cout << p << endl;
示例7-3-1
修改陣列和指標的內容
7.3.2
內容複製與比較
來判斷,應該用標準庫函式strcmp 進行比較。
比較的不是內容而是位址,應該用庫函式strcmp 來比較。
陣列…char a = "hello";
char b[10];
不 能用b = a;
… 指標…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
… 示例7-3-2
陣列和指標的內容複製與比較
7.3.3
計算記憶體容量
用運算子sizeof 可以計算出陣列的容量(位元組數)。 示 例7-3-3 (a )中,sizeof(a) 的值是12 (注 意別忘了
』/0』
)。指標p 指向a ,但是sizeof(p) 的 值卻是4 。
這是因為sizeof(p) 得 到的是乙個指標變數的位元組數,相當於sizeof(char*) ,而不是p 所指的記憶體容量。c++/c 語 言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。
注意當陣列作為函式的引數進 行傳遞時,該陣列自動退化為同型別的指標。
示例7-3-3 (b ) 中,不論陣列a 的容量是多少,sizeof(a) 始 終等於sizeof(char *) 。
char a = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12
位元組 cout<< sizeof(p) << endl; // 4位元組
示例7-3-3(a
) 計算陣列和指標的內 存容量
void func(char a[100])
《 高質量C 程式設計指南 》學習重點十
物件 object 是類 class 的乙個例項 instance 如果將物件比作房子,那麼類就是房子的設計圖紙。所以物件導向設計的重點是類的設計,而不是物件的設計。對於 c 程式而言,設計孤立的類是比較容易的,難的是正確設計基類及其派生類。本章僅僅論述 繼承 inheritance 和 組合 co...
《 高質量C 程式設計指南 》學習重點六
有了 malloc free 為什麼還要 malloc與 free 是c c 語言的標準庫函式,new delete 是c 對於非內部資料型別的物件而言,光用 maloc free 無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行析構函式。由於 malloc fr...
《 高質量C 程式設計指南 》學習重點十
物件 object 是類 class 的乙個例項 instance 如果將物件比作房子,那麼類就是房子的設計圖紙。所以物件導向設計的重點是類的設計,而不是對 象的設計。對於c 程式而言,設計孤立的類是比較容易的,難的是正確設計基類及其派生類。本章僅僅論述 繼承 inheritance 和 組合 co...