2008年4月5日 星期六

活用 g_object_weak_ref 避免 memory leak

Standard
這是一個常見又惱人的問題 - Memory Leak 記憶體洩漏。常見的情況是軟體用一段時間後,記憶體使用量肥大,在許多功能複雜的軟體之中,不免會看到這類的情形。不過我們不能完全怪罪軟體設計者〔雖然他們還是佔很重要的因素〕,對程式開發者來說,要達成完全 Leak-free ,幾乎是不可能的事,因為除了自己寫的程式之外,各種 system call 都有可能造成 memory leak 的發生〔如 pango〕。

不過話說回來,system 裡各種 library 所造成的 memory leak 畢竟還是少量,軟體開發者所造成的問題佔著大多數,一個真實的例子:從 Firefox 每次 release 就一直在修正 memory leak 就可以發現修也修不完。

來探討 memory leak 的形成原因,顧名思義就是漏水,記憶體外漏產生了無法控制的記憶體區塊。舉個簡單的例子:
void func()
{
char *p;

p = (char *)malloc(12);
p = (char *)malloc(10);

free(p);
}

此例的程式碼會出現 12 bytes 的 memory leak,因為第一次 malloc 取得的記憶體區塊未被釋放。換句話說,在第二次 malloc 之後,我們已經失去前一次記憶體區塊的位址了,該區塊對我們來說,已經是再也無法控制的記憶體區塊〔因為不知道位址〕,但他仍然會在程式結束前活在那。如此一來,要是我們重複呼叫此 function ,每次都會造成 12 bytes 的浪費。

當然,這是明顯可以看出 memory leak 存在的例子,有很多情況是很難發現問題的,尤其是在結構複雜的程式中。

有一個情況是這樣,在 Gtk+ 程式的設計中,常會臨時建立一個 structure 傳給 Widget event 的 callback function,程式碼大致如下:
s = malloc(struct cb_s);
g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(cb_func), s);

和傳統的程式不同,我們不能在 g_signal_connect 之後就用 g_free() 去釋放 s,因為在 menu_item 這 Widget 銷毀之前,我們都有可能因事件觸發而使用到 s。所以,這時就要用到一個特別的 g_object function:
void g_object_weak_ref(GObject *object, GWeakNotify notify, gpointer data);

這 g_object function 可以設定 object 被銷毀後,將會呼叫什麼 GWeakNotify function 做一些後續處理的動作。通常必需是自己設計一個 function 來呼叫,就如同 g_signal_connect 的 callback function 那樣。不過在這例子中,可以有個漂亮的特殊用法:
g_object_weak_ref(menu_item, g_free, s);

將 Glib 的 g_free 當做 GWeakNotify function 來使用,是一個很漂亮的方式。在 menu_item destroy 之後,便會使用 g_free 將 s 給釋放掉。如此就不會在,Widget 銷毀後因為 s 而產生 memory leak 了。