dbx コマンドによるデバッグ

メモリーリークの検査

メモリーリークとは、プログラムで使用するために割り当てられているが、プログラムのデータ領域中のいずれも指していないポインタをもつ、動的に割り当てられたメモリーブロックを言います。そのようなブロックは、メモリーのどこに存在しているかプログラムにわからないため、プログラムに割り当てられていても使用することも解放することもできません。RTC はこのようなブロックを検知し、報告します。

メモリーリークは仮想メモリーの使用を増やし、一般的にメモリーの断片化を招きます。その結果、プログラムやシステム全体のパフォーマンスが低下する可能性があります。

メモリーリークは、通常、割り当てメモリーを解放しないで、割り当てブロックへのポインタを失うと発生します。メモリーリークの例を以下に示します。


void
foo()
{
    char *s;
    s = (char *) malloc(32);
 
    strcpy(s, "hello world");
 
    return; /* s が解放されていない。foo が戻るとき、malloc されたブロック
               を指しているポインタが存在しないため、ブロックはリークする*/
}

リークは、API の不正な使用が原因で起こる可能性があります。


void
printcwd()
{

    printf("cwd = %s¥n", getcwd(NULL, MAXPATHLEN));

    return; /* libc の関数 getcwd() は、最初の引数が NULL の場合 malloc
               された領域へのポインタを返す。プログラムは、これを解放する必要
               がある。この場合、ブロックが解放されていないため、結果的にリー
               クになる。*/
}

メモリーリークを防ぐには、必要のないメモリーは必ず解放します。また、メモリーを確保するライブラリ関数を使用する場合は、メモリーを解放することを忘れないでください。

解放されていないブロックを「メモリーリーク」と呼ぶこともあります。ただし、この定義はあまり使用されません。プログラムが短時間で終了する場合でも、通常のプログラミングではメモリーを解放しないからです。プログラムにそのブロックに対するポインタがある場合、RTC はそのようなブロックはメモリーリークとして報告しません。

メモリーリーク検査の使用


注 -

RTC のリーク検査を実行するには、標準の libcmalloc、free、realloc のいずれか、またはこれらの関数にもとづいた割り当て関数を使用する必要があります。


RTC は、以下のメモリーリークエラーを検出できます。

各エラーと例の詳細については、この章に後述される「RTC エラー」の項を参照してください。

リークの可能性

RTC が「リークの可能性」として報告するエラーには 2 種類あります。1 つは、ブロックの先頭を指すポインタが検知されず、ブロックの内部を指しているポインタが見つかった場合です。これは、ブロック中のアドレス (aib) エラーとして報告されます。このようなブロック内部を指すポインタが見つかった場合は、プログラムに実際にメモリーリークが発生しています。ただし、プログラムによってはポインタに対して故意にそのような動作をさせている場合があり、これは当然メモリーリークではありません。RTC はこの違いを判別できないため、本当にリークが発生しているかどうかはユーザー自身の判断で行う必要があります。

もう 1 つのリークの種類は、レジスタ中のアドレス (air) エラーとして報告されるリークです。これは、ある領域を指すポインタがデータ空間中には存在せず、レジスタ内に存在する場合です。レジスタがブロックを不正に指していたり、古いメモリーポインタが残っている場合には、実際にメモリーリークが発生しています。ただし、コンパイラが最適化のために、ポインタをメモリーに書き込むことなく、レジスタのブロックに対して参照させることがありますが、この場合はメモリーリークではありません。プログラムが最適化され、showleaks コマンドでエラーが報告された場合のみ、リークでない可能性があります。


注 -

RTC リーク検査では、標準 libc malloc/free/realloc 関数またはアロケータをこれらの関数に基いて使用する必要があります。ほかのアロケータについては、「RTC での修正継続機能の使用」を参照してください。


リークの検査

メモリーリーク検査がオンの場合、メモリーリークの走査は、テスト中のプログラムが終了する直前に自動的に実行されます。検出されたリークはすべて報告されます。プログラムを、kill コマンドによって強制的に終了してはなりません。次に、典型的なメモリーリークエラーによるメッセージを示します。


メモリーリーク (mel):
大きさ 1024 バイト のリークのあるブロックをアドレス 0x20c20 に発見
割り当て時のスタックの状態
      [1] _getcwd() at 0xef6be224
      [2] printcwd() 行番号 7 "printcwd.c"
      [3] main() 行番号 13 "printcwd.c"

呼び出しスタック位置のハイパーテキストリンクをクリックすると、ソースコードの該当する行がエディタウィンドウに表示されます。

プログラムには通常 main (FORTRAN 77 では MAIN) 手続きが存在します。プログラムは exit(3) が呼び出されるか、main から返った時点で終了します。いずれの場合でも、main のすべての局所変数はプログラムが停止するまでスコープから出ず、それらを指す特定のヒープブロックはすべてメモリーリークとして報告されます。

main() に割り当てられているヒープブロックはプログラムでは解放しないのが一般的です。これらのヒープブロックはプログラムが停止するまでスコープ内に残り、プログラムの停止後オペレーティングシステムによって自動的に解放されるためです。main() に割り当てられたブロックがメモリーリークとして報告されないようにするには、main() が終了する直前にブレークポイントを設定しておきます。プログラムがそこで停止したとき、RTC の showleaks コマンドを実行すれば、main() とそこで呼び出されるすべての手続きで参照されなくなったヒープブロックのすべてが表示されます。

メモリーリークの報告を理解する

リーク検査機能をオンにすると、プログラムの終了時に自動リークレポートが生成されます。プログラムが kill コマンドによって強制終了されていなければ、可能性のあるリークがすべて報告されます。デフォルトでは、dbxenv 変数 rtc_mel_at_exit で指定してある簡易リークレポートが生成されます。

レポートは、リークのサイズによってソートされます。実際のメモリーリークが最初に報告され、次に可能性のあるリークが報告されます。詳細レポートには、スタックトレース情報の詳細が示されます。行番号とソースファイルが使用可能であれば、これらも必ず含まれます。

次のメモリーリークエラー情報が、2 種類の報告のどちらにも含まれます。

リークしたブロックが割り当てられた場所 

リークしたブロックのアドレス 

リークしたブロックのサイズ 

割り当て時の呼び出しスタック。check -frames によって制約される

簡易レポートはエラー情報をテーブルに出しますが、詳細レポートでは、各エラーについて個別のエラーメッセージが示されます。どちらもソースコード内のエラーの位置にハイパーテキストでリンクされます。

次に、対応する簡易メモリーリークレポートを示します。


実際のリークの報告       (実際のリーク:        1  合計サイズ:       1 バイト)

合計     ブロック  リーク      割り当て呼出しスタック
サイズ   数        ブロック
                   アドレス
======   ========  =========   =====================================
    1        1     0x20f18     mel < leaks < main 

起こり得るリークの報告  (起こり得るリーク:      1  合計サイズ      4 バイト)

合計   ブロック リーク      割り当て呼出しスタック
サイズ  数    ブロック
          アドレス
======   ========  =========   =====================================
    4       1      0x20ef8     aib < leaks < main 

次に、典型的な詳細リークレポートを示します。


実際のリークの報告       (実際のリーク:        1  合計サイズ:       1 バイト)

メモリーリーク (mel):
大きさ 1 バイト のリークのあるブロックをアドレス 0x20f18 に発見
割り当て時のスタックの状態:
        [1] mel() 行番号 19 "leaks.c"
        [2] leaks() 行番号 6 "leaks.c"
        [3] main() 行番号 7 "main.c"


起こり得るリークの報告  (起こり得るリーク:      1  合計サイズ      4 バイト)

メモリーリークの可能性 -- ブロック中のアドレス (aib):
大きさ 4 バイト のリークのあるブロックをアドレス 0x20ef8 に発見
割り当て時のスタックの状態:
        [1] aib() 行番号 11 "leaks.c"
        [2] leaks() 行番号 5 "leaks.c"
        [3] main() 行番号 7 "main.c"

リークレポートの生成

showleaks コマンドを使用すると、いつでもリークレポートを要求することができます。このコマンドは、前回の showleaks コマンド以降の新しいメモリーリークを報告するものです。

リークレポート

リークレポートの数が多くなるのを避けるため、RTC は同じ場所で割り当てられたリークを自動的に 1 つにまとめて報告します。1 つにまとめるか、それぞれ各リークごとに報告するかは、一致フレーム数引数によって決まります。この引数は、check -leaks コマンドを実行する際は -match m オプション、showleaks コマンドを実行する際は -m オプションで指定します。呼び出しスタックが 2 つ以上のリークを割り当てる際に m 個のフレームと一致した場合は、リークは 1 つにまとめて報告されます。

以下の 3 つの呼び出しシーケンスを考えてみます。

ブロック 1 

ブロック 2 

ブロック 3 

[1] malloc

[1] malloc

[1] malloc

[2] d() at 0x20000

[2] d() at 0x20000

[2] d() at 0x20000

[3] c() at 0x30000

[3] c() at 0x30000

[3] c() at 0x31000

[4] b() at 0x40000

[4] b() at 0x41000

[4] b() at 0x40000

[5] a() at 0x50000

[5] a() at 0x50000

[5] a() at 0x50000

これらのブロックがすべてメモリーリークを起こす場合、m の値によって、これらのリークを別々に報告するか、1 つのリークが繰り返されたものとして報告するかが決まります。m が 2 のとき、ブロック 1 とブロック 2 のリークは 1 つのリークが繰り返されたものとして報告されます。これは、malloc() の上にある 2 つのフレームが共通しているためです。ブロック 3 のリークは、c() のトレースがほかのブロックと一致しないので別々に報告されます。m が 2 より大きい場合、RTC はすべてのリークを別々に報告します (malloc はリークレポートでは表示されません)。

一般に、m の値が小さければリークのレポートもまとめられ、m の値が大きければまとめられたリークレポートが減り、別々のリークレポートが生成されます。

メモリーリークの修正

RTC からメモリーリーク報告を受けた場合にメモリーリークを修正する方法についてのガイドラインを以下に示します。リークの修正で最も重要なことは、リークがどこで発生したかを判断することです。作成されるリーク報告は、リークが発生したブロックの割り当てトレースを示します。リークが発生したブロックは、ここから割り当てられたことになります。次に、プログラムの実行フローを見て、どのようにそのブロックを使用したかを調べます。ポインタが失われた箇所が明らかな場合は簡単ですが、それ以外の場合は showleaks コマンドを使用してリークの検索範囲を狭くすることができます。showleaks コマンドは、デフォルトでは前回このコマンドを実行した後に検出されたリークのみを報告するため、showleaks を繰り返し実行することにより、ブロックがリークを起こした可能性のある範囲が狭まります。