有時候 C++ 程式會莫明地 crash 在呼叫 method 的時候, 像是 obj->method()。檢查 obj 的值有幾種情況:
- NULL: 一看就知道有問題
- 某種規律的數字, 像是 0x23232323, 0xCDCDCDCD: 99% 有問題, 還沒猜錯過
- 特定的位置, 如 0xDEADBEEF: 看了也知道有問題, 這是 debugger 特意覆寫的值
- 看起來像個正常的指標, 可能是 dangle pointer
需要注意的是, 透過 dangle pointer 呼叫 method, 不一定會 crash。不過呼叫 virtual method 時一定會 crash, 因為 destructor 會重置 virtual method table 內的值 (相關文章見《C++ 執行後掛在 __cxa_pure_virtual》)。
以下是一個測試例子:
$ cat a.cpp #includeclass Rect { public: Rect(int x, int y, int w, int h) : x(x), y(y), w(w), h(h) {} int area() const { return w * h; }; virtual int area2() const { return w * h; }; private: int x, y, w, h; }; int main(void) { int w, h; scanf("%d%d", &w, &h); Rect *r = new Rect(10, 20, w, h); printf("area: %d\n", r->area()); printf("area2: %d\n", r->area2()); delete r; printf("area: %d\n", r->area()); printf("area2: %d\n", r->area2()); return 0; } $ ./a 30 40 area: 1200 area2: 1200 area: 1200 Segmentation fault (core dumped)
以前我判斷 dangle pointer 的小撇步是印出 pointer 指向的 object 的某些 member field, 若出現很怪的數字, 十之八九是 dangle pointer。不過有時還是會出包, 像上面的例子就是反例。看來用 virtual method 一定會 crash 這點做判斷, 會更準確。
在不改 code 的前提下, 可以設中斷點在懷疑是 dangle pointer 的 object 的 destructor, 再重執行程式。但若有大量同 class 的 object, 在 destructor log object 的記憶體位置會比較方便。此外, Scott 提到可以用 glibc MALLOC_PERTURB_, 看起來滿不錯的。不過我試了一下覺得不太管用, 沒有深入研究。
若使用的 gcc 比較新 (>=4.8, Ubuntu 14.04 有支援) 或用 llvm 的話, 可以考慮用 AddressSanitizer, 重點是執行效率比以往的替代工具快上不少。
2014-07-23 更新
Thinker 提到對於有 virtual method 的 object, 多數平台可以藉由印出 vptr 的值來確認是否已變成 dangle pointer。下面是用這個概念檢查 dangle pointer 的例子:
$ gdb a ... (gdb) b 22 # break on "delete r" Breakpoint 1 at 0x400746: file a.cpp, line 22. (gdb) r Starting program: /home/fcamel/dev/tmp/a 30 40 area: 1200 area2: 1200 Breakpoint 1, main () at a.cpp:22 22 delete r; (gdb) p (void**)r[0] $1 = (void **) 0x400950 (gdb) n 23 printf("area: %d\n", r->area()); (gdb) p (void**)r[0] $2 = (void **) 0x0 (gdb) p *r $3 = {_vptr.Rect = 0x0, x = 10, y = 20, w = 30, h = 40}
雖然 vptr (pointer to Virtual Method Table) 在 object 內的位置依實作而定, 在自己的平台實驗一下, 就可以用這招配合 debugger 除錯了。
沒有留言:
張貼留言