C/C++内存检测工具valgrind--memcheck
Valgrind簡介
Valgrind是運行在Linux上的一套基于仿真技術的程序調試和分析工具,作者是獲得過Google-O’Reilly開源大獎的Julian Seward,它包含一個內核 —— 一個軟件合成的CPU,和一系列的小工具,每個工具都可以完成一項任務──調試,分析,或測試等,內存檢測,我們可以使用它的工具:Memcheck。
?
Valgrind安裝
方法 1. valgrind官網:http://valgrind.org下載
方法 2. Ubuntu: sudo apt-get install valgrind
?
Memcheck的檢測范圍
它可以用來檢測c/c++程序中出現的內存問題,所有對內存的讀寫都會被檢測到,一切對malloc()/free()/new/delete的調用都會被捕獲。所以,它能檢測以下問題:
1). 對未初始化內存的使用;
2). 讀/寫釋放后的內存塊;
3). 讀/寫超出malloc等分配的動態內存范圍;
4). 讀/寫不適當的棧中內存塊;
5). 內存泄漏,指向一塊內存的指針永遠丟失;
6). 不正確的malloc/free或new/delete匹配;
7). memcpy()相關函數中的dst和src指針重疊問題。
?
Memcheck檢測使用步驟及注意事項
1、在編譯程序的時候打開調試模式(gcc編譯器的-g選項),以便顯示行號,編譯時去掉-O1 -O2等優化選項;檢查的是C++程序的時候,考慮加上選項: -fno- inline ,這樣它函數調用鏈會很清晰。
2、執行:valgrind --tool=memcheck --leak-check=full --log-file=./log.txt ./YourProgram
3、程序運行結束,查看 log.txt 中的結果。
結果分析:
Valgrind(memcheck)包含這7類錯誤,黑體為一般的錯誤提示:
1、illegal read/illegal write errors 非法讀取/非法寫入錯誤
2、use of uninitialised values 使用未初始化的區域
3、use of uninitialised or unaddressable values in system calls 系統調用時使用了未初始化或不可尋址的地址
4、illegal frees 非法的釋放
5、when a heap block is freed with an inappropriate deallocation function 分配和釋放函數不匹配
6、overlapping source and destination blocks 源和目的內存塊重疊
7、memory leak detection 內存泄漏檢測
? 7.1 Still reachable 內存指針還在還有機會使用或者釋放,指針指向的動態內存還沒有被釋放就退出了
? 7.2 Definitely lost 確定的內存泄露,已經不能夠訪問這塊內存
? 7.3 Indirectly lost 指向該內存的指針都位于內存泄露處
? 7.4 Possibly lost 可能的內存泄露,仍然存在某個指針能夠訪問某塊內存,但該指針指向的已經不是該內存首位置
? 7.5 Suppressed 某些庫產生的錯誤不予以提示,這些錯誤會被統計到suppressed項目
?
測試案例:
最近剛剛用最小堆實現了一個定時容器,寫完之后就使用Valgrind的memcheck工具檢測了一些內存,log日志如下:
==38716== Memcheck, a memory error detector ==38716== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==38716== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==38716== Command: ./test_heap_timer_new ==38716== Parent PID: 17817 ==38716== ==38716== Invalid write of size 4 ==38716== at 0x10BFF8: HeapTimer<Event>::setPos(int) (heap_timer.hpp:59) ==38716== by 0x10BF30: HeapTimerContainer<Event>::percolateDown(int) (heap_timer.hpp:334) ==38716== by 0x10BAD3: HeapTimerContainer<Event>::popTimer() (heap_timer.hpp:300) ==38716== by 0x10B79C: HeapTimerContainer<Event>::tick() (heap_timer.hpp:196) ==38716== by 0x10B34C: main (test_heap_timer_new.cpp:307) ==38716== Address 0x4db2478 is 24 bytes inside a block of size 32 free'd ==38716== at 0x483D1CF: operator delete(void*, unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==38716== by 0x10BA8B: HeapTimerContainer<Event>::popTimer() (heap_timer.hpp:296) ==38716== by 0x10B79C: HeapTimerContainer<Event>::tick() (heap_timer.hpp:196) ==38716== by 0x10B34C: main (test_heap_timer_new.cpp:307) ==38716== Block was alloc'd at ==38716== at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==38716== by 0x10B808: HeapTimerContainer<Event>::addTimer(long) (heap_timer.hpp:212) ==38716== by 0x10AC77: acceptConn(Event*, ITimerContainer<Event>*) (test_heap_timer_new.cpp:175) ==38716== by 0x10B2B5: main (test_heap_timer_new.cpp:293) ==38716== ==38716== ==38716== HEAP SUMMARY: ==38716== in use at exit: 0 bytes in 0 blocks ==38716== total heap usage: 10 allocs, 10 frees, 78,304 bytes allocated ==38716== ==38716== All heap blocks were freed -- no leaks are possible ==38716== ==38716== For lists of detected and suppressed errors, rerun with: -s ==38716== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)可以看到有一個4字節的無效內存寫入,是在HeapTimer::setPos(int) (heap_timer.hpp:59)里面,我找了半天,最終發現了原因。
原代碼鏈接:高性能定時器1——最小堆實現_Peerless__的博客-CSDN博客
代碼如下:
template <typename _UData> void HeapTimerContainer<_UData>::tick() {std::cout << "----------tick----------" << std::endl;HeapTimer<_UData> *tmp = _array[0];time_t cur = getMSec();// 循環處理到期的定時器while(!isEmpty()){if(!tmp){break;}// 如果定時器沒到期,則退出循環if(tmp->getExpire() > cur){break;}tmp->handleTimeOut();// 將堆頂元素刪除,同時生成新的堆頂定時器popTimer();tmp = _array[0];} }template <typename _UData> void HeapTimerContainer<_UData>::popTimer() {if(isEmpty()){return;}if(_array[0]){delete _array[0];// 將原來的堆頂元素替換為堆數組中最后一個元素_array[0] = _array[--_size];// 對新的堆頂元素執行下濾操作percolateDown(0);} }// 最小堆的下濾操作,它確保數組中以第hole個節點作為根的子樹擁有最小堆性質 template <typename _UData> void HeapTimerContainer<_UData>::percolateDown(int hole) { /* if(_size == 0){return;}*/HeapTimer<_UData> *temp = _array[hole];int child = 0;for(; ((hole * 2 + 1) <= _size - 1); hole = child){child = hole * 2 + 1;if((child < (_size - 1)) && (_array[child + 1]->getExpire() < _array[child]->getExpire())){child++;}if(_array[child]->getExpire() < temp->getExpire()){_array[hole] = _array[child];_array[hole]->setPos(hole); // 調整定時器的位置時,重新設置timer中pos保存的其在數組中的位置}else {break;}}_array[hole] = temp;_array[hole]->setPos(hole); }上面代碼的邏輯是,tick函數被調用就會從最小堆中取出最近要超時的定時器(也就是堆頂元素),檢查是否超時,如果超時就執行回調函數,然后調用popTimer彈出堆頂元素。在popTimer中delete掉了剛超時的定時器,并將堆數組中最后一個元素放到堆頂,然后調用percolateDown來調整最小堆。Valgrind生成的日志中提示在HeapTimer::setPos(int) (heap_timer.hpp:59)出錯了。后來發現,如果堆中只有一個元素,這段代碼_array[0] = _array[–_size];也就變成了_array[0] = _array[0];然后就是在percolateDown函數中對已經delete掉的對象調用了setPos函數。也就是使用了已經delete掉的對象,但是程序運行的時候竟然沒有崩潰,這是個挺大的隱患的,沒有崩潰更為恐怖,因為可能下一次就要崩潰。解決方案就是在percolateDown函數中加一個數組是否為空的判斷,如果為空直接返回就好了。
總結
以上是生活随笔為你收集整理的C/C++内存检测工具valgrind--memcheck的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下内存检测工具:asan
- 下一篇: qt5使用内存检测工具vld查看内存泄漏