Valgrind 與 memcheck 記憶體檢查工具
/ Valgrind -> VAL-grind (rhymes with 'pal-grinned') /
C 裡的記憶體臭蟲很狡猾:你可能讀過陣列的結尾、在釋放記憶體之後還使用它、或忘了釋放它,而程式往往照常跑得好好的——直到很久以後,在某個不相干的地方,它才當掉或給出錯誤答案。你需要一位警覺的稽查員,在你碰到不該碰的記憶體那一刻就察覺。Valgrind 就是那位稽查員。你把程式跑在 Valgrind「裡面」,它的工具 memcheck 會盯著每一個記憶體操作、回報你出錯的確切行號。
具體來說,Valgrind 是一個把你的程式跑在一顆模擬 CPU 上的框架:它不是單純啟動你的執行檔,而是解譯每一條機器指令,在每次記憶體存取周圍插入檢查。預設的工具 memcheck 追蹤記憶體的哪些位元組是可定址的(你被允許碰它們)、哪些是已初始化的(它們裝著你寫進去的值)。當你的程式讀寫一個有效區塊之外的地方、讀取從未初始化的記憶體、在 free() 之後使用一個指標、或把同一個區塊釋放兩次時,memcheck 當場逮到、並印出帶著指向出問題那一行之堆疊追蹤的錯誤。在結束時它還回報洩漏:你配置了卻從未釋放、且再也無法到達的區塊。代價是速度:因為每條指令都被解譯與插樁,程式在 Valgrind 下大約慢 10 到 50 倍。
為何重要:Valgrind/memcheck 找出整類整類的記憶體臭蟲——釋放後使用、緩衝區溢位、未初始化的讀取、洩漏、重複釋放——而且不必重新編譯,常把令人費解的「隨機當機」變成「在第 88 行對 4 個位元組的無效讀取,發生在一個於第 71 行被釋放的區塊內」。誠實的提醒:它很慢,所以是用於測試、不是生產環境;它在執行期對執行檔插樁、而非理解你的原始碼,所以它回報的是症狀位置,可能離成因很遠;而且它無法找出在你測試執行從未走到的程式路徑上的臭蟲。在純速度與某些臭蟲類別上,它如今大致被編譯器消毒器所補足,那些消毒器在編譯期插樁、跑得快得多。
$ valgrind ./a.out 對一個讀取超過 10 元素陣列一格的程式,會印出類似:「Invalid read of size 4 ... at main.c:14」,並在結束時印出「definitely lost: 40 bytes in 1 blocks」,指你 malloc 了卻從未釋放的記憶體——兩者都附上確切的行號。
Valgrind 逮到一次越界讀取與一次洩漏,每個都附上確切的原始碼行。
Valgrind 把未經修改的執行檔跑在模擬的 CPU 上,所以不必重新編譯、但慢 10 到 50 倍——適合測試、不適合生產環境。它回報的是那次壞存取發生的地方,而那可能離當初設下壞指標的那個邏輯錯誤很遠。