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


181

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

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

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


16
valgrindを使用して、ソースコードではなく、コンパイルされたプログラムをテストします。
Tony

6
下記の@RageDの答えは正しいのですが、それを受け入れませんか?
Pratik Singhal

1
リークは、あなたやることに失敗したことによって引き起こされます。割り当てられたメモリを解放します。したがって、Valgrindはリークの「どこ」にあるかを示すことはできません。割り当てられたメモリが不要になった場所を知っているのはあなただけです。ただし、どのメモリ割り当てがfree()dでないかを通知することにより、プログラムを介してそのメモリの使用を追跡することで、free()dをどこで取得する必要があるかを判断できます。よくある間違いは、割り当てられたメモリを解放せずに関数をエラー終了することです。
MikeW 2016年

回答:


296

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:速度よりも有用な出力を優先します。これは、初期化されていない値の原因を追跡します。これは、メモリエラーに非常に役立ちます。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)

私は、リークを持っていますが、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バイトがありました。どうやってそうなった?エラーレポートはちょうど言う mainmalloc。より大きなプログラムでは、それを探すのは非常に面倒です。これは、実行可能ファイルのコンパイル方法が原因です。何が問題だったかに関する詳細を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 はリークしたメモリを割り当てるコードの正確な行をポイントします!(言い回しは重要です。リークの正確な場所ではないかもしれませんが、リークされたものです。トレースは、どこを見つけるの役立ちます 。)

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が閉じ括弧をまだ入力していないことに気付くように、リーク/エラーが相互にリンクする場合があります。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、割り当てられたメモリを実際に別の場所に移動し、ポインタの場所を変更できることに気づいています。次に、配列がどこに移動されたか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)無効な書き込みである理由です。これは範囲外です。無効な書き込みは、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)無効な書き込みと同様に、再び範囲外です。無効な読み取りも、off-by-oneエラーの一般的な結果です。割り当て操作の右側を見てください。


オープンソース(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、すべての関数から与えられたメモリを私たちまたは「解放」することを意味するものではありません。実際、私が上で書いたこのコードは、「無効な読み取り」と「無効な書き込み」の両方をスローします。これらのエラーは、のdecref行を削除すると解消されますvalue

どうして?変数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のメモリアロケータはどのように機能しますか?実際にメモリリークとメモリエラーは何ですか?それらはsegfaultsとどう違うのですか?Valgrindはどのように機能しますか?これらのいずれかをお持ちの場合は、好奇心を養ってください:


4
はるかに良い答え、これは受け入れられた答えではないのは残念です。
A. Smoliak

そのようなことをするのは良い習慣だと思います、私はいくつか自分でしました
A. Smoliak '27年

1
この回答にスターを付けて、今後の参考にできますか?よくできました!
2018年

ないmemcheckツールは、デフォルトで有効になっていますか?
abhiarora

@abhiaroraはい。manページには、ことを教えてくれるmemcheckデフォルトのツールである:--tool=<toolname> [default: memcheck]
ジョシュアDetwiler

146

これを試して:

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

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


「your_program」とはどういう意味ですか?このソースコードの場所またはアプリケーション名(apkファイルなど)ですか?
HoangVu 2016年

7
your_program==アプリケーションの実行に使用する実行可能ファイルの名前またはコマンド。
RageD 2016年


1

次のように、.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ログファイルが生成されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.