想必對于C語言有研究的小夥伴都知道,記憶體洩露幾乎是很難避免的,不管是老手還是新手,都存在這個問題,那今天我們就來展開說說C語言記憶體洩露示例剖析,一起往下學習吧。
正确的記憶體管理的重要性
存在記憶體錯誤的 C 和 C++ 程式會導緻各種問題。如果它們洩漏記憶體,則運作速度會逐漸變慢,并最終停止運作;如果覆寫記憶體,則會變得非常脆弱,很容易受到惡意使用者的攻擊。從 1988 年著名的莫裡斯蠕蟲 攻擊到有關 Flash Player 和其他關鍵的零售級程式的最新安全警報都與緩沖區溢出有關:“大多數計算機安全漏洞都是緩沖區溢出”,Rodney Bates 在 2004 年寫道。
在可以使用 C 或 C++ 的地方,也廣泛支援使用其他許多通用語言(如 Java™、Ruby、Haskell、C#、Perl、Smalltalk 等),每種語言都有衆多的愛好者和各自的優點。但是,從計算角度來看,每種程式設計語言優于 C 或 C++ 的主要優點都與便于記憶體管理密切相關。與記憶體相關的程式設計是如此重要,而在實踐中正确應用又是如此困難,以緻于它支配着面向對象程式設計語言、功能性程式設計語言、進階程式設計語言、聲明性程式設計語言和另外一些程式設計語言的所有其他變量或理論。
與少數其他類型的常見錯誤一樣,記憶體錯誤還是一種隐性危害:它們很難再現,症狀通常不能在相應的源代碼中找到。例如,無論何時何地發生記憶體洩漏,都可能表現為應用程式完全無法接受,同時記憶體洩漏不是顯而易見。
是以,出于所有這些原因,需要特别關注 C 和 C++ 程式設計的記憶體問題。讓我們看一看如何解決這些問題,先不談是哪種語言。
記憶體錯誤的類别
首先,不要失去信心。有很多辦法可以對付記憶體問題。我們先列出所有可能存在的實際問題:
•記憶體洩漏
•錯誤配置設定,包括大量增加 free() 釋放的記憶體和未初始化的引用
•懸空指針
•數組邊界違規
這是所有類型。即使遷移到 C++ 面向對象的語言,這些類型也不會有明顯變化;無論資料是簡單類型還是 C 語言的 struct 或 C++ 的類,C 和 C++ 中記憶體管理和引用的模型在原理上都是相同的。以下内容絕大部分是“純 C”語言,對于擴充到 C++ 主要留作練習使用。
記憶體洩漏
在配置設定資源時會發生記憶體洩漏,但是它從不回收。下面是一個可能出錯的模型(請參見清單 1):
清單 1. 簡單的潛在堆記憶體丢失和緩沖區覆寫
複制代碼 代碼如下:
void f1(char *explanation) { char *p1; p1 = malloc(100); sprintf(p1,"The f1 error occurred because of '%s'.", explanation); local_log(p1); }
您看到問題了嗎?除非 local_log() 對 free() 釋放記憶體具有不尋常的響應能力,否則每次對 f1 的調用都會洩漏 100 位元組。在記憶棒增量分發數兆位元組記憶體時,一次洩漏是微不足道的,但是連續操作數小時後,即使如此小的洩漏也會削弱應用程式。
在實際的 C 和 C++ 程式設計中,這不足以影響您對 malloc() 或 new 的使用,本部分開頭的句子提到了“資源”不是僅指“記憶體”,因為還有類似以下内容的示例(請參見清單 2)。FILE 句柄可能與記憶體塊不同,但是必須對它們給予同等關注:
清單 2. 來自資源錯誤管理的潛在堆記憶體丢失
複制代碼 代碼如下:
int getkey(char *filename) { FILE *fp; int key; fp = fopen(filename, "r"); fscanf(fp, "%d", &key); return key; }
fopen 的語義需要補充性的 fclose。在沒有 fclose() 的情況下,C 标準不能指定發生的情況時,很可能是記憶體洩漏。其他資源(如信号量、網絡句柄、資料庫連接配接等)同樣值得考慮。
記憶體錯誤配置設定
錯誤配置設定的管理不是很困難。下面是一個錯誤配置設定示例(請參見清單 3):
清單 3. 未初始化的指針
複制代碼 代碼如下:
void f2(int datum) { int *p2; *p2 = datum; ... }
關于此類錯誤的好消息是,它們一般具有顯著結果。在 AIX® 下,對未初始化指針的配置設定通常會立即導緻segmentation fault 錯誤。它的好處是任何此類錯誤都會被快速地檢測到;與花費數月時間才能确定且難以再現的錯誤相比,檢測此類錯誤的代價要小得多。
在此錯誤類型中存在多個變種。free() 釋放的記憶體比 malloc() 更頻繁(請參見清單 4):
清單 4. 兩個錯誤的記憶體釋放
複制代碼 代碼如下:
void f3() { char *p, *pp; p = malloc(10);
pp=p;
free(p); ... free(pp); } void f4() { char *p;
...
free(p); }
這些錯誤通常也不太嚴重。盡管 C 标準在這些情形中沒有定義具體行為,但典型的實作将忽略錯誤,或者快速而明确地對它們進行标記;總之,這些都是安全情形。
懸空指針
懸空指針比較棘手。當程式員在記憶體資源釋放後使用資源時會發生懸空指針(請參見清單 5):
清單 5. 懸空指針
複制代碼 代碼如下:
void f8() { struct x *xp; xp = (struct x *) malloc(sizeof (struct x)); xp.q = 13; ... free(xp); ... return xp.q; }
傳統的“調試”難以隔離懸空指針。由于下面兩個明顯原因,它們很難再現:
•即使影響提前釋放記憶體範圍的代碼已本地化,記憶體的使用仍然可能取決于應用程式甚至(在極端情況下)不同程序中的其他執行位置。
•懸空指針可能發生在以微妙方式使用記憶體的代碼中。結果是,即使記憶體在釋放後立即被覆寫,并且新指向的值不同于預期值,也很難識别出新值是錯誤值。
懸空指針不斷威脅着 C 或 C++ 程式的運作狀态。
數組邊界違規
數組邊界違規十分危險,它是記憶體錯誤管理的最後一個主要類别。回頭看一下清單 1;如果 explanation 的長度超過 80,則會發生什麼情況?回答:難以預料,但是它可能與良好情形相差甚遠。特别是,C 複制一個字元串,該字元串不适于為它配置設定的 100 個字元。在任何正常實作中,“超過的”字元會覆寫記憶體中的其他資料。記憶體中資料配置設定的布局非常複雜并且難以再現,是以任何症狀都不可能追溯到源代碼級别的具體錯誤。這些錯誤通常會導緻數百萬美元的損失。
.棘手的記憶體洩漏
複制代碼 代碼如下:
static char *important_pointer = NULL; void f9() { if (!important_pointer) important_pointer = malloc(IMPORTANT_SIZE); ... if (condition) important_pointer = malloc(DIFFERENT_SIZE); ... }
do not傳回局部指針變量或者局部變量的指針,除非是一個static局部變量
char *f0() { char temp[]="123456789"; //加上static 才是正确的
return temp; }
以上就是小編分享的c語言記憶體洩露示例剖析,希望本文的内容對大家學習C語言能帶來一定的幫助,如果有疑問大家可以留言交流。