參考代碼
Tracer的變種
File Line Tracer 和 Dbghelp Tracer 各自的優劣
首先,從性能上,File Line Tracer 所需的信息均來自編譯期,運行時除了程序棧和hash之外不存在新的調用開銷,而Dbghelp的信息則來自於運行時,開銷自然比File Ln Tracer大得多。
然後,File Line Tracer需要define new,這會引入一些小麻煩,Dbghelp tracer則不需要這個東西。define new後面繼續展開。
再然後,File Line Tracer只知道”分配內存的當前語句所在的文件和行號“,但DbgHelp還可以給出”分配內存的當前Call stack,更利於快速定位到錯誤的分配“。
File Line Tracer 的 define new 和因此帶來的問題
define new最大的問題在於需要確保在new之前調用#define new DEBUG_NEW宏。
頭文件包含關係比較亂的時候,這一點就比較難受。如果有預編譯頭,相對好辦點,只要在預編譯頭的第一行加入這句話即可。但沒有預編譯頭的時候,這就需要用戶自己維護其正確性了。
否則,萬一有.h裏new,.cpp裏delete這樣的情況(或者相反),而define new又發生在這個.h之後,就極易發生誤判的情況,某個new明明被刪除了,但是沒有記錄下來,於是誤報了內存泄露,或者某次刪除發現刪的不是相應的new……
頭文件順序真是C++永遠的痛,傷不起啊傷不起……
Tracer優化
Cookie優化
無論哪個tracer,都增加了一些代碼的開銷。
如果內存分配器也是自己寫的,這裏就方便一點,內存分配的時候可以多在前分出一些小Cookie,在這些小Cookie裏面記錄所需的信息。然後需要這些信息的時候,只需要向前尋址若干字節,獲取出Cookie,這樣的性能是最高的,但會引入兩個問題:
一,全套內存分配要自己做,dlmalloc、tlmalloc等成熟的第三方分配用不了了,這也是爲什麼我的例子代碼裏使用了獨立的Tracer的原因。
二,要確認當前訪問的內存是由本分配器分配出來的,一旦new與delete不配對,這種問題就會如雨後春筍般出現。一個不太好但是大部分情況下適用的方案是Cookie裏記錄一個魔數,每次訪問時先判斷魔數能不能對上,畢竟大部分場合下,內存裏的數據正好對上魔數的可能性極低。但這種問題還是防不勝防。當你提供的並非全套解決方案,而是隻是一個小模塊,且會被其它人代碼級而非二進制級引用時,這個問題就可能會變得愈發突出。
多進程優化
所佔內存優化
如果把Trace按”變種“章節所說的,改成全截獲,永不銷燬,那麼接下來要面臨的問題就是,一旦分配多起來,這內存佔用就呼呼地往上漲了。
這時也有方案,就是把Tracer的改成不在本進程處理,而是將消息通過TCP連接發給其它應用程序去截獲和處理。但這樣的話,對DbgHelper來說,發送的信息就必須得包括Call stack的全文信息。否則另一個應用程序如果得到的只是Stack標記,要反解出來就要麻煩很多很多。好在發送線程可以做在另一個線程立,而每個Call stack全文信息獲取一次以後可以緩存下來,Call stack全文信息的數量相對於分配數總歸是少的,不是嗎?
Tracer的跨模塊調用
Tracer跨模塊後,就會變成比較頭疼的問題。
如果所有的模塊全都是您自己維護的,那麼您倒是可以保證您的Tracer公平地在每個模塊裏使用,不會出現問題。
但是如果您製作的是一個可發佈的、相對精煉且功能相對單一的可分發程序包時,Tracer就要萬分小心了。
首先,用戶未必希望開啓此模塊的Tracer功能,這就需要提供專門的MEMLEAK DEBUG版本。
然後,如果用戶使用了Tracer,就要確保由Tracer new出來的內存,也要由Tracer監督其delete,這裏就需要守住”本模塊new本模塊刪“的原則,但是原則嘛,自然是說着容易做着難了。你中槍了木有?
再然後,如果用戶不想使用Tracer,或者不想讓用戶使用Tracer,就需要在分發版本的include文件中排除掉Tracer,而.h裏相應使用的就需要用宏屏蔽。這個應該大家都是這麼做的,權當廢話好了。
最後,如果用戶想用你的Tracer,相信我,這只是噩夢的開始……你永遠無法知道用戶會怎麼使用你提供的庫和接口……所以唯一能做的,只能是讓他們無從選擇。
跨模塊是一個相當讓人絞盡腦汁的特性,如果您自己完全可控的項目,建議還是不要在這個路上走得太遠。畢竟,適應的纔是最好的,您說呢?