web-dev-qa-db-ja.com

valgrindを使用してメモリリークを見つけるにはどうすればよいですか?

Valgrindを使用してプログラムのメモリリークを見つけるにはどうすればよいですか?

誰かが私を助けて、手順を実行する手順を説明してください?

私はUbuntu 10.04を使用しており、プログラムa.cを持っています。手伝ってください。

111
user484457

Valgrindの実行方法

Valgrindを効果的に使用する方法とメモリリークを解決する方法について、より詳細な説明を作成したいと思います。 OPをin辱するのではなく、この質問に出くわしてLinuxにまだ慣れていない人のために、Valgrindをシステムにインストールする必要があるかもしれません。

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

ValgrindはC/C++コードですぐに使用できますが、適切に構成されていれば他の言語でも使用できます(Pythonの場合は this を参照)。

valgrindを実行するには、実行可能ファイルを引数として(プログラムへのパラメーターとともに)渡します。

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

これにより、プログラムの実行終了時に(できれば)次のようなレポートが生成されます。

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)

リークがありますが、WHERE

したがって、メモリリークが発生し、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バイトが失われました。どうやってそうなった?エラーレポートには、mainおよびmallocとだけ記載されています。大規模なプログラムでは、追い詰めるのは非常に面倒です。 これは、実行可能ファイルがどのようにコンパイルされたかによるものです。実際に、問題の詳細を1行ずつ取得できます。デバッグフラグを使用してプログラムを再コンパイルします(ここでは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はコードの正確な行を指しますリークしたメモリを割り当てます! (言葉遣いは重要です。漏れがある場所は正確ではないかもしれませんが、whatが漏れました。トレースはwhere。)

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型の問題です。
    • メモリを解放した後、メモリにアクセスしたり、メモリに書き込んだりしないでください。
  • Valgrindは、エラーの場所を常に正確に指し示すとは限りません。場合によっては、多くのエラーが発生し、そのうちの1つを解決すると、他のカスケードが解決されることがあります。
    • メモリエラーがある「問題のある」コードに依存する/依存するコード内の関数をリストします。プログラムの実行を追跡し(おそらく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が実際に割り当てられたメモリを別の場所に移動し、ポインタの位置を変更できることに気付いています。その後、配列の移動先をarray->dataに通知せずにresizeArrayを残します。

無効な書き込み

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)が無効な書き込みです。これは範囲外です。無効な書き込みは、off-by-oneエラーの一般的な結果です。割り当て操作の左側を見てください。

無効な読み取り

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文字列を読み取り、解析します。作成では、ライブラリ呼び出しを使用して解析を行います。 JSONは自身のネストされた構造を含むことができるため、Janssonは必要な割り当てを動的に行います。ただし、これは、decrefまたはすべての関数から与えられたメモリを「解放」するという意味ではありません。実際、上で書いたこのコードは、「無効な読み取り」と「無効な書き込み」の両方をスローします。 decrefvalue行を取り出すと、これらのエラーはなくなります。

どうして?変数valueは、Jansson APIでは「借用参照」と見なされます。 Janssonはそのメモリを追跡します。そして、単にdecref JSON構造を互いに独立させる必要があります。ここでのレッスン:ドキュメントを読む。本当に。理解するのが難しいこともありますが、彼らはなぜこれらのことが起こるのかを教えています。代わりに、このメモリエラーについて 既存の質問 があります。

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%確実)

他の人のコードを使用している場合、どのようにリークを見つけるのですか?
チャンスはすでに他の誰かが見つけたものです。 Googleをお試しください!それが失敗した場合、上記で説明したスキルを使用します。それが失敗し、主にAPI呼び出しとほとんど独自のスタックトレースが表示される場合は、次の質問を参照してください。

私ではない漏れを見つけました。私は何かする必要がありますか?
はい!ほとんどのAPIには、バグや問題を報告する方法があります。それらを使用してください!プロジェクトで使用しているツールへの還元にご協力ください!


参考文献

長く滞在してくれてありがとう。この答えにたどり着く幅広い層の人々に対応しようとしたので、あなたが何かを学んだことを願っています。途中であなたが尋ねたことを願っています:Cのメモリアロケータはどのように機能しますか?実際にメモリリークとメモリエラーとは何ですか?彼らはセグメンテーション違反とどう違うのですか? Valgrindはどのように機能しますか?これらのいずれかがあったら、あなたの好奇心を養ってください:

161
Joshua Detwiler

これを試して:

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

Valgrindがインストールされている限り、プログラムを調べて、何が問題なのかを通知します。それはあなたにポインターとあなたの漏れが見つかるかもしれないおおよその場所を与えることができます。セグメンテーション違反の場合は、gdbを実行してみてください。

138
RageD

以下を実行できます。

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