簡體   English   中英

如何使用 valgrind 查找內存泄漏?

[英]How do I use valgrind to find memory leaks?

如何使用 valgrind 查找程序中的內存泄漏?

請有人幫助我並描述執行該程序的步驟?

我正在使用 Ubuntu 10.04 並且我有一個程序ac ,請幫幫我。

如何運行 Valgrind

不是侮辱 OP,而是對於那些提出這個問題並且仍然是 Linux 新手的人 -您可能必須在您的系統上安裝 Valgrind

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind 很容易用於 C/C++ 代碼,但如果配置正確,甚至可以用於其他語言(請參閱 Python 的此內容)。

要運行 Valgrind ,請將可執行文件作為參數(以及任何參數)傳遞給程序。

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

簡而言之,這些標志是:

  • --leak-check=full :“將詳細顯示每個單獨的泄漏”
  • --show-leak-kinds=all :在“完整”報告中顯示所有“確定的、間接的、可能的、可達的”泄漏類型。
  • --track-origins=yes--track-origins=yes有用的輸出而不是速度。 這會跟蹤未初始化值的來源,這對於內存錯誤非常有用。 如果 Valgrind 慢得令人無法接受,請考慮關閉。
  • --verbose :可以告訴您程序的異常行為。 重復更多細節。
  • --log-file :寫入文件。 當輸出超過終端空間時很有用。

最后,您希望看到如下所示的 Valgrind 報告:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated

All heap blocks were freed -- no leaks are possible

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

我有泄漏,但在哪里

所以,你有一個內存泄漏,Valgrind 沒有說任何有意義的事情。 也許,像這樣:

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (in /home/Peri461/Documents/executable)

我們也來看看我寫的C代碼:

#include <stdlib.h>

int main() {
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;
}

好吧,丟失了 5 個字節。 它怎么發生的? 錯誤報告只是說mainmalloc 在一個更大的程序中,追捕這將是非常麻煩的。 這是因為可執行文件是如何編譯的 我們實際上可以逐行獲取有關出錯的詳細信息。 使用調試標志重新編譯您的程序(我在這里使用gcc ):

gcc -o executable -std=c11 -Wall main.c         # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c  # add -ggdb3 to it

現在有了這個調試版本, Valgrind 指向分配泄漏內存的確切代碼行 (用詞是很重要的:它可能不是正是你的泄漏,但什么得到了泄露的跟蹤可以幫助您找到在哪里。)

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (main.c:4)

調試內存泄漏和錯誤的技術

  • 使用www.cplusplus.com 它有關於 C/C++ 函數的大量文檔。
  • 內存泄漏的一般建議:
    • 確保動態分配的內存確實被釋放了。
    • 不要分配內存而忘記分配指針。
    • 除非舊內存被釋放,否則不要用新指針覆蓋指針。
  • 內存錯誤的一般建議:
    • 訪問和寫入您確定屬於您的地址和索引。 內存錯誤不同於泄漏; 它們通常只是IndexOutOfBoundsException類型的問題。
    • 釋放內存后不要訪問或寫入內存。
  • 有時,您的泄漏/錯誤可以相互關聯,就像 IDE 發現您尚未鍵入結束括號一樣。 解決一個問題可以解決其他問題,所以尋找一個看起來很好的罪魁禍首並應用以下一些想法:

    • 列出代碼中依賴/依賴於具有內存錯誤的“違規”代碼的函數。 跟蹤程序的執行(甚至可能在gdb ),並查找前置條件/​​后置條件錯誤。 這個想法是在關注分配內存的生命周期的同時跟蹤程序的執行。
    • 嘗試注釋掉“違規”的代碼塊(在合理范圍內,這樣您的代碼仍然可以編譯)。 如果 Valgrind 錯誤消失,您就找到了它的位置。
  • 如果所有其他方法都失敗了,請嘗試查找。 Valgrind 也有文檔

常見泄漏和錯誤概覽

注意你的指針

60 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
   by 0x4005E4: resizeArray (main.c:12)
   by 0x40062E: main (main.c:19)

和代碼:

#include <stdlib.h>
#include <stdint.h>

struct _List {
    int32_t* data;
    int32_t length;
};
typedef struct _List List;

List* resizeArray(List* array) {
    int32_t* dPtr = array->data;
    dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
    return array;
}

int main() {
    List* array = calloc(1, sizeof(List));
    array->data = calloc(10, sizeof(int32_t));
    array = resizeArray(array);

    free(array->data);
    free(array);
    return 0;
}

作為助教,我經常看到這個錯誤。 學生使用局部變量而忘記更新原始指針。 這里的錯誤是注意到realloc實際上可以將分配的內存移動到其他地方並更改指針的位置。 然后我們離開resizeArray而不告訴array->data數組移動到哪里。

無效寫入

1 errors in context 1 of 1:
Invalid write of size 1
   at 0x4005CA: main (main.c:10)
 Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
   at 0x4C2B975: calloc (vg_replace_malloc.c:711)
   by 0x400593: main (main.c:5)

和代碼:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* alphabet = calloc(26, sizeof(char));

    for(uint8_t i = 0; i < 26; i++) {
        *(alphabet + i) = 'A' + i;
    }
    *(alphabet + 26) = '\0'; //null-terminate the string?

    free(alphabet);
    return 0;
}

請注意,Valgrind 將我們指向上面注釋的代碼行。 大小為 26 的數組的索引為 [0,25],這就是為什么*(alphabet + 26)是無效寫入的原因——它越界了。 無效寫入是一對一錯誤的常見結果。 查看賦值操作的左側。

無效讀取

1 errors in context 1 of 1:
Invalid read of size 1
   at 0x400602: main (main.c:9)
 Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x4005E1: main (main.c:6)

和代碼:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* destination = calloc(27, sizeof(char));
    char* source = malloc(26 * sizeof(char));

    for(uint8_t i = 0; i < 27; i++) {
        *(destination + i) = *(source + i); //Look at the last iteration.
    }

    free(destination);
    free(source);
    return 0;
}

Valgrind 將我們指向上面的注釋行。 看看這里的最后一次迭代,這是
*(destination + 26) = *(source + 26); . 但是, *(source + 26)再次越界,類似於無效寫入。 無效讀取也是一對一錯誤的常見結果。 查看賦值操作的右側。


開源 (U/Dys) 烏托邦

我怎么知道什么時候泄漏是我的? 當我使用別人的代碼時,如何找到我的漏洞? 我發現了一個不屬於我的泄漏; 我應該做些什么嗎? 都是合理的問題。 首先,2 個真實世界的例子展示了 2 類常見的遭遇。

Jansson :一個 JSON 庫

#include <jansson.h>
#include <stdio.h>

int main() {
    char* string = "{ \"key\": \"value\" }";

    json_error_t error;
    json_t* root = json_loads(string, 0, &error); //obtaining a pointer
    json_t* value = json_object_get(root, "key"); //obtaining a pointer
    printf("\"%s\" is the value field.\n", json_string_value(value)); //use value

    json_decref(value); //Do I free this pointer?
    json_decref(root);  //What about this one? Does the order matter?
    return 0;
}

這是一個簡單的程序:它讀取一個 JSON 字符串並解析它。 在制作中,我們使用庫調用來為我們進行解析。 Jansson 動態地進行必要的分配,因為 JSON 可以包含自身的嵌套結構。 然而,這並不意味着我們要decref或“釋放”每個函數給我們的內存。 事實上,我上面寫的這段代碼同時拋出了“無效讀取”和“無效寫入”。 當您取出valuedecref行時,這些錯誤就會消失。

為什么? 變量value在 Jansson API 中被視為“借用引用”。 Jansson 會為您跟蹤其內存,您只需對彼此獨立的 JSON 結構進行decref 這里的教訓:閱讀文檔 真的。 有時很難理解,但他們會告訴您為什么會發生這些事情。 相反,我們有關於此內存錯誤的現有問題

SDL :圖形和游戲庫

#include "SDL2/SDL.h"

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    SDL_Quit();
    return 0;
}

這段代碼有什么問題? 它始終為我泄漏約 212 KiB 的內存。 花點時間考慮一下。 我們打開然后關閉 SDL。 回答? 沒有任何錯誤。

這乍一聽可能很奇怪 說實話,圖形很亂,有時你不得不接受一些泄漏作為標准庫的一部分。 這里的教訓是:您無需消除所有內存泄漏 有時您只需要抑制泄漏,因為它們是您無能為力的已知問題 (這不是我允許忽略你自己的泄漏!)

對空虛的回答

我怎么知道什么時候泄漏是我的?
這是。 (無論如何,99% 肯定)

當我使用別人的代碼時,如何找到我的漏洞?
很有可能其他人已經找到了。 試試谷歌! 如果失敗,請使用我上面給你的技能。 如果失敗並且您主要看到 API 調用而很少看到您自己的堆棧跟蹤,請參閱下一個問題。

我發現了一個不屬於我的泄漏; 我應該做些什么嗎?
是的! 大多數 API 都有報告錯誤和問題的方法。 使用它們! 幫助回饋您在項目中使用的工具!


進一步閱讀

謝謝你陪我這么久。 我希望你已經學到了一些東西,因為我試圖傾向於得到這個答案的廣泛人群。 我希望你一路上問過一些事情:C 的內存分配器是如何工作的? 什么是內存泄漏和內存錯誤? 它們與段錯誤有何不同? Valgrind 是如何工作的? 如果你有這些,請一定要滿足你的好奇心:

嘗試這個:

valgrind --leak-check=full -v ./your_program

只要安裝了 valgrind,它就會通過您的程序並告訴您出了什么問題。 它可以為您提供可能發現泄漏的指針和大致位置。 如果您遇到了段錯誤,請嘗試通過gdb運行它。

你可以運行:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]

您可以在 .bashrc 文件中創建別名,如下所示

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

所以每當你想檢查內存泄漏時,只需簡單地做

vg ./<name of your executable> <command line parameters to your executable>

這將在當前目錄中生成一個 Valgrind 日志文件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM