Valgrindを使用してプログラムのメモリリークを見つけるにはどうすればよいですか?
誰かが私を助けて、手順を実行する手順を説明してください?
私はUbuntu 10.04を使用しており、プログラムa.c
を持っています。手伝ってください。
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)
したがって、メモリリークが発生し、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)
IndexOutOfBoundsException
型の問題です。gdb
でさえ)、前提条件/事後条件エラーを探します。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)
は再び範囲外です。無効な読み取りは、オフバイワンエラーの一般的な結果でもあります。割り当て操作の右側を見てください。
漏れがいつ私のものかを知るにはどうすればよいですか?他の人のコードを使用している場合、どのようにリークを見つけるのですか?私ではない漏れを見つけました。私は何かする必要がありますか?すべては正当な質問です。まず、2つの一般的な遭遇のクラスを示す2つの実世界の例。
#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構造を互いに独立させる必要があります。ここでのレッスン:ドキュメントを読む。本当に。理解するのが難しいこともありますが、彼らはなぜこれらのことが起こるのかを教えています。代わりに、このメモリエラーについて 既存の質問 があります。
#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はどのように機能しますか?これらのいずれかがあったら、あなたの好奇心を養ってください:
これを試して:
valgrind --leak-check=full -v ./your_program
Valgrindがインストールされている限り、プログラムを調べて、何が問題なのかを通知します。それはあなたにポインターとあなたの漏れが見つかるかもしれないおおよその場所を与えることができます。セグメンテーション違反の場合は、gdb
を実行してみてください。
以下を実行できます。
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]