內存分配和釋放幾乎是所有程序的基本需求,同時也是最常出問題的地方之一,有誰沒有聽說過內存泄漏問題呢?通過遵循以下幾條簡單的規則,你可以避免很多常見的內存分配問題。
原則1: 誰申請誰釋放
?說明
原則上,哪個對象、函數申請的系統堆內存,在用完后應由原申請者釋放,所謂“誰借的誰還”。
但這個原則說起來容易,真正要完全做到卻很麻煩。比如經常遇到這樣的情況:程序內部要傳遞一些信息,這些信息一般都是需要時產生,用完消除,所以應該存放在系統堆內存中。但是這樣就導致該內存的產生者必須知道什么時候所有人都不再使用該內存,即需要回收了。這可不是一句兩句能實現的,拿捏不好就會發生回收之后又有人要用、或所有人都不再用了卻沒有及時回收的情況。
當然,傳遞也可以用內存拷貝方式(而不是上述的傳遞指針或引用方式),這樣,就不存在回收的問題了。但當信息很大或引用者很多時,拷貝的代價就成了問題。這引出另一個有名的計算機問題,即程序運行時,數據像波浪一樣從內存中的一處移動到另一處,可以想象,這種移動毫無意義,肯定不是好的解決方案。
原則2: 當對象消亡時確保指針成員指向的系統堆內存全部被釋放
?說明
否則會造成內存泄漏。
原則3:malloc、free, new、delete,new[ ]、delete[ ]要成對使用
?說明
1) 調用new所包含的動作:
從系統堆中申請恰當的一塊內存。
若是對象,調用相應類的構造函數,并以剛申請的內存地址作為this參數。
2) 調用new[n]所包含的動作:
從系統堆中申請可容納n個對象外加一個整型的一塊內存。
將n記錄在額外的那個整型內存中(其位置依賴于不同的實現,有的在申請內存塊開頭,有的在末尾)。
調用n次構造函數初始化這塊內存中的n個連續對象。
3) 調用delete所包含的動作:
若是對象,調用相應類的析構函數(在delete參數所指的內存處)。
將該內存返還系統堆。
4) 調用delete[]所包含的動作:
從new[]記錄n的地方將n值找出。
調用n次析構函數析構這塊內存中的n個連續對象。
將這一整塊內存(包括記錄n的整型)歸還系統堆。
可以看出,分配和釋放單個元素與數組的操作不同,這就是為什么一定要成對使用這些操作符的原因。
原則4: 確保所有new(malloc)出來的東西適時被delete(free)掉
?說明
不需要的內存不能被及時釋放(回系統),就是大家常聽到的內存泄漏(memory1eak)。狹義的內存泄漏是指分配的內存不再使用,但卻永遠不被釋放。從更廣的意義上說,沒有及時釋放也是內存泄漏,只是程度較輕而已。
內存泄漏不管有多小,最終都會耗盡所有內存(只要運行的時間足夠長)。內存泄漏的問題極難查找,因為當出現問題時,內存己經耗盡,此時CPU運行什么程序(哪怕是系統程序),什么程序就崩潰。可以看出,崩潰時報告的出錯信息與引起問題的代碼毫無關聯。另外內存耗盡的時間是不確定的,且一般會是一個較長的時間(幾天或幾星期都可能),這就更增加了再現和定位問題的難度。
目前有一些工具可以幫助查找內存泄漏問題,比如Rational公司的RMfy。這些工具大大提高了查找內存泄漏的能力。但防患于未然才是治本之道。
原則5: 當所指的內存釋放后,指針應有一個合理的值
?說明
除非該指針變量本身將要消失(out of scope),否則應置為空(NULL)。
?例子
void Channel_T::disconnect (void)
{
delete i_pConnection; // 釋放資源
i_pConnection=NULL; // 指針指向一個合理的值
//…
/ *
* 注意:delete不會也不可能將i_Connection置成NULL,
*因為delete掉的是i_Connection指向的一片內存空間
*而不是它本身
*/
}
原則6: 記住給字符串結束符申請空間
?例子
pName=new char[strLen+1]; // +1是為了給“\0”留出空間
?說明
字符串有隱含的結束符“\0”,申請空間時別忘了。
這種數組越界的錯誤很難查。因為一方面越界的概率一般很小,不好再現。另一方面,即使越界,也不一定導致程序出錯,因為只有越界后要訪問其他進程的內存空間時,系統才意識到有問題。或者越界修改自己的其他內存值而造成嚴重問題,比如導致該值成為非法,否則程序還能向前推進。越界后是否出錯還跟編譯器或編譯時的參數設定有關,比如有的編譯器或參數設定(如打開優化功能)會要求內存變量一個緊挨一個排列。另一些卻選擇諸如字對齊方式,這會留下一些空隙,如果越界正好落在空隙中,就什么問題也沒有。所以,有時發現,調試版(打開debug編譯選項)不出問題,優化版總出問題。
- 第一章 概述
- 1.1規范制定原則
- 1.2 術語定義
- 1.3 文件命名組織
- 1.3.1文件命名
- 1.3.2文件注釋
- 第二章 編碼風格
- 第三章 注釋
- 3.1 注釋概述
- 3.2 文檔型注釋
- 3.3 類c注釋
- 3.4 單行注釋
- 3.5 注釋標簽
- 第四章 聲明
- 4.1每行聲明數
- 4.2初始化
- 4.3位置
- 4.4類和接口的聲明
- 4.5字段的聲明
- 第五章 命名規范
- 5.1命名概述
- 5.2大小寫規則
- 5.3縮寫
- 5.4命名空間
- 5.5類
- 5.6接口
- 5.7屬性 (Attribute)
- 5.8枚舉 (Enum)
- 5.9參數
- 5.10方法
- 5.11屬性 (property)
- 5.12事件
- 5.13 常量 (const)
- 5.14 字段
- 5.15 靜態字段
- 5.16 集合
- 5.17 措詞
- 第六章 語句
- 6.1每行一個語句
- 6.2 復合語句
- 6.3 return 語句
- 6.4 if、 if-else、if else-if 語句
- 6.5 for、foreach 語句
- 6.6 while 語句
- 6.7 do - while 語句
- 6.8 switch - case 語句
- 6.9 try - catch 語句
- 6.10 using 塊語句
- 6.11 goto 語句
- 第七章 函數與類
- 第八章 內存分配和釋放
- 第九章 兼容性
- 第十章 控件命名規則
- 10.1 命名方法
- 10.2 主要控件名簡寫對照表
- 附錄一: 匈牙利命名法