C++霧中風景番外篇3:GDB與Valgrind ,調試代碼內存的工具

寫 C++的同窗想必有太多和內存打交道的血淚經驗了,經常被 C++的內存問題攪的焦頭爛額。(寫 core 的經驗了)有不少同窗一見到 core 就兩眼一抹黑,不知所措了。筆者 入""C++以後,在調試 C++代碼的過程之中,學習了很多調試代碼內存的工具。但願借這個機會來介紹一下筆者經常使用的工具,GDB,Valgrind等等,相信你們經過好好運用這些工具,能更好的馴服內存這匹"野馬"。html

1.利用 GDB 調試 CoreDump

CoreDump時一個二進制的文件,進程發生錯誤崩潰時,內核會產生一個瞬時的快照,記錄該進程的內存、運行堆棧狀態等信息保存在core文件之中。作個簡單的類比,core 文件至關於飛機運行時的"黑匣子",可以幫助咱們更好的調試 C++程序的問題。OK,接下來筆者將介紹一下若是利用GDB 來調試 CoreDump的文件。java

  • CoreDump 文件的大小

首先咱們先肯定一下操做系統是否會產生 CoreDump 文件。經過ulimit -c獲取 core 文件的限制大小:
查看 core 文件的大小限制app

上面顯示筆者電腦的 core 文件的大小是0,咱們須要調整一下。經過ulimit調整爲無限制。固然這種調整是臨時的,reboot 以後就恢復爲0了。函數

ulimit -c ulimited

若是須要永久修改,能夠經過/etc/security/limits.conf 來修改 core 文件的大小。工具

  • CoreDump 文件的生成路徑
    默認狀況下,core dump生成的文件名爲core,並且就在程序當前目錄下。經過修改/proc/sys/kernel/core_pattern能夠控制core文件保存位置和文件格式。(建議將後綴改成進程號) 筆者這裏簡單起見,不進行修改了。性能

  • 編寫core 代碼,這裏筆者利用線程訪問了空指針
#include <unistd.h>
#include <thread>

void core() {
    char* ch = nullptr;
    *ch = 'a';
}

int main() {
    auto t1 = std::thread(core);
    sleep(5);
    return 0;
}
  • 編譯運行該代碼,產生段錯誤,生成了 core 文件
    圖片.png學習

  • 利用 GDB 調試 core 文件
    調試 core 文件須要利用原生編譯出的二進制文件調試。這裏有一點須要注意的,若是編譯 C++文件之時沒有加-g的編譯選項,core 文件的調試內容會不夠完整。筆者這裏建議開啓對應的編譯選項,這會致使對應的二進制文件變大,編譯時間變長。(生產環境能夠考慮關閉)使用gdb 二進制文件 core 文件打開 core 文件。測試

利用 gdb 調試 core 文件

core 文件列出了兩個線程的信息。咱們須要判斷對應的問題代碼的定位,接下來咱們一塊兒來梳理一下:
info thread查看線程的運行狀況,在這裏咱們就能夠判斷代碼 core 在什麼線程之中了,若是仍是沒法肯定,能夠經過thread apply all bt列出更加詳盡的堆棧信息。優化

用 info thread 查看線程運行狀況
利用 thread apply all bt 顯示詳盡的堆棧信息
經過上述信息能夠確認,thread 1的代碼存在問題。咱們經過thread 1切換到 thread 1,用bt顯示堆棧信息繼續追查:
Thread 1的堆棧信息ui

以後咱們來看看使人生疑的棧內容,這裏顯然棧0是咱們懷疑的代碼,用frame 1查看。
對應存在『問題』的語句

好了,這裏咱們找到了引發問題罪魁禍首的代碼,訪問了空指針。

小結

程序運行的 core 文件是咱們調試代碼十分重要依據,經過 GDB 能夠很好的給出咱們修改代碼的線索和參考,熟悉掌握GDB 的調試技巧,可以大大解放咱們調試問題代碼的生產力。

2.利用Valgrind判斷內存泄露

亡羊補牢不如未雨綢繆,與其等到出現程序崩潰時使用 GDB 來調試解決,不如事前確認代碼之中可能引起的問題。因此筆者接下來要介紹一款來自大不列顛的C++代碼分析神器:Valgrind。(Valgrind的做者也經過開發Valgrind得到了第二屆Google-O'Reilly開源代碼大獎~~~)
Valgrind 十分強大,適用於內存分析,泄漏檢測、鎖分析,性能評估。筆者也只掌握了一些基本的入門使用。但願這裏可以拋磚引玉,更多複雜的用法煩請參考官方文檔

Valgrind的安裝

Valgrind的安裝很簡單,筆者的發行版帶了對應的 deb 包。經過 apt-get 的包管理工具就能夠直接安裝了,其餘的發行版也能夠做爲參考。

sudo apt-get install valgrind
Valgrind的使用

與 GDB 相似,Valgrind 一樣推薦使用-g做爲編譯參數。可以更好的對代碼進行分析。這裏咱們依舊使用以前的例子進行測試:

valgrind ./untitiled

下面是 Valgrind 的分析結果:
valgrind 的分析結果

這裏有顯示Invalid write of size 1,說明這裏有一個不合法的寫入,而且寫入了1個字節的內容。也就是指的是咱們以前代碼之中寫入空指針的行爲。

接下來咱們要展現 Valgrind更增強大的功能。它展現了程序的內存使用狀況,而且給出總結:
valgrind 對內存的分析
這裏列出了多種的內存泄露狀況:

  • definitely lost: 確定的內存泄漏,這表示在程序退出時,有內存沒有回收,可是也沒有指針指向該內存。這種狀況最爲嚴重。

  • indirectly lost: 間接的內存泄漏,如類之中定義的指針指向的內存沒有回收。這種狀況和上述相同。

  • possibly lost: 可能出現內存泄漏。這種狀況須要仔細排查,可能代碼沒有問題,也可能有異常的內存泄露。

  • still reachable: 程序沒主動釋放內存,在退出時候該內存仍能訪問到。這種狀況通常問題不大,由於程序退出以後操做系統會回收程序的內存,因此這種狀況通常問題不大。

這裏沒有給出具體泄露的內容,須要加入參數--leak-check=full將完整的結果打印出來,會指出對應的引發內存泄露的具體代碼,能夠繼續深刻分析。

代碼調優

這裏進行代碼調優的時,須要利用qcachegrind來進行分析。首先筆者先進行安裝:

sudo apt-get install qcachegrind

以後咱們調用Valgrind來生成運行數據:

valgrind --tool=callgrind -v main(須要分析的程序)

運行以後在目錄下生成對應的分析數據,咱們用qcachegrind 打開,這裏用的代碼是筆者以前實現的 SkipList

qcachegrind callgrind.out.29235

接下來咱們來分析對應的結果:
valgrind 的分析結果

上圖顯示了各個函數的被調用的耗時百分比,咱們能夠選取對性能感興趣的函數來進行深刻分析。咱們下面繼續分析其中一個函數被調用和它使用函數的性能狀況
insert 的函數被外調用的狀況
insert 函數調用函數的狀況與耗時分析

因此經過上述數據,咱們能夠給出性能分析的證據和線索,依據這些信息來更好的優化咱們代碼的性能。

3.小結

本文介紹了亡羊補牢的工具 GDB,也簡介了未雨綢繆的Valgrind 。經過上述工具對C++程序更加深刻分析。工欲善其事,必先利其器,但願你們也能好好掌握這些提供生產力的工具,讓 C++再也不惱人

相關文章
相關標籤/搜索