天天看點

安全程式設計-c++野指針和記憶體洩漏

  盡管C++ 野指針和記憶體洩漏一直被诟病,但是在實時性很強的應用場合,c++ 仍然是不二之選。遊戲伺服器開發仍然使用c++ 作為主語言,但是大多結合動态腳本技術,一方面規避了野指針和記憶體洩露,一方面獲得了開發效率和擴充性的紅利。但腳本技術不是本文的讨論重點,事實上關于c++ 與 lua的技術文章我也一直在整理中,将會另文别述。今天主要說說在使用c++過程中,如何避免和解決野指針和記憶體洩漏問題。

  野指針的出現會導緻程式崩潰,這是每個人都不願意看到的。Linux會生成coredump檔案,可用gdb分析。Win下可以注冊unexception擷取調用堆棧,将錯誤資訊寫到檔案中。先分析一下通常出現野指針的場景:

  問題就在于,m_attack有值,但是對應的對象已經被銷毀了。這是大部分野指針出現原因。分析類之間關系可知,monster_t 和 player_t是0-1的關系,monster_t引用player_t,但是player_t甚至都不知道有一個(或N個)monster 引用了自己。是以當player被銷毀時,很難做到把所有引用該player_t的地方全部重置。這種問題其實比較常見,比如player中引用connection,而connection又是被網絡層管理生命周期的,也同樣容易産生野指針情況。常見的解決方式是:

另外一種與之相似的方式:

  梳理野指針的産生原因後,我們其實需要的是這樣的指針:

  一種指針,引用了另一個對象的位址(不然就不是指針了),當目标對象銷毀時,該指針自然指向null,而不需要目标對象主動通知重置。

幸運的是,這種指針已經有了,就是weak_ptr; 在boost庫中,sharedptr,scopedptr,weakptr統稱為smartptr。可以盡量使用智能指針,避免野指針。本人建議盡量使用shared_ptr結合weak_ptr使用。Scoped_ptr本人使用的較少,隻是在建立線程對象的時候使用,正好符合不能複制的語義。使用shared_ptr和weak_ptr的示例代碼:

有人問monster_t為什麼不直接使用shared_ptr,如果使用shared_ptr就不符合現實的模型了,monster_t顯然不應該控制player_t的生命周期,如果使用了shared_ptr,那麼可能導緻player_t被延遲析構,甚至會導緻記憶體暴漲。這也是shared_ptr的使用誤區,是以本人建議盡量shared_ptr和weak_ptr結合用,否則野指針問題解決了,記憶體洩漏問題又來了。

野指針問題可以通過采用良好的程式設計範式,盡量規避,但總計c++規避記憶體洩漏的方法卻很為難,簡單而言盡量保證對象的配置設定和釋放(分别)是單個入口的,這樣大部分問題都可以攔截在code review階段。那麼怎麼檢測記憶體洩漏呢?

首先說明本方法差別于valgrind等工具,該工具是調試期進行的檢測,本文探究的是運作期的檢測,确切說是運作期定時輸出所有對象的數量到日志中。

首先定義配置設定、釋放對象的接口:

為了節省篇幅,這裡隻列舉了三種構造的代碼,當配置設定對象時,對應的類型數量增加1,obj_counter 使用原子操作為每一種類型記錄其數量。

相應的當對象被釋放的時候,對應的對象數量減一,示例代碼如下:

這樣就做到了所有的對象的數量都被記錄了,可以定時的将對象數量輸出到檔案:

檔案内容data:

obj,num,20120606-17:01:41

dumy,1111

foo,222

obj,num,20120606-18:01:41

dumy,11311

foo,2422

obj,num,20120606-19:01:41

dumy,41111

foo,24442

安全程式設計-c++野指針和記憶體洩漏

總結:

野指針可以使用shared_ptr和weak_ptr結合使用來盡量規避。

使用shared_ptr要盡量小心,否則可能導緻對象無法釋放,導緻記憶體洩漏。

可以定時輸出目前所有對象的數量,來分析是否有記憶體洩漏,或者記憶體洩漏是有哪些對象引起的。

本文介紹了記錄所有對象的方法,除了可以分析記憶體洩漏外,也不失為資料分析的一種方法。需要注明的是,本方法不能替代valgrind工具,二者作用不同。

TYPE_NAME 的實作參考

繼續閱讀