C言語メモ / VisualStudio / リークチェック方法


Last update: 2012-02-17 (金) 10:49:38

C言語メモ/VisualStudio?/リークチェック方法
Tag: VisualStudio


メモリ リークの検出と特定

メモリを動的に割り当てたり解放したりする機能は、C/C++ プログラミングの最も強力な機能の 1 つです。ただし、最大の長所は最大の弱点にもなりえます。このことは、C/C++ アプリケーションに関して確かに事実であり、メモリ処理において発生する問題は最も発生頻度の高いバグに分類されています。

最も微妙で検出しにくいバグの 1 つがメモリ リークです。メモリ リークとは、割り当て済みのメモリを正しく解放できない状態を指します。わずかなメモリ リークが 1 回だけ発生する場合は、問題として認識されないこともあります。しかし、大量のメモリ リークが発生したり、メモリ リークが累積する類のプログラムでは、パフォーマンスの低下 (しかも低下が進行する) から完全なメモリ不足まで、さまざまな兆候が現れる可能性があります。さらに悪いことに、メモリ リークの発生原因であるプログラムが大量のメモリを消費してしまうため、別のプログラムにも問題が波及し、問題の本当の原因を究明することが困難になります。また、さして問題とならないメモリ リークでも、別の問題の兆候を示している可能性があります。

Visual Studio のデバッガと C ランタイム (CRT) ライブラリには、メモリ リークを検出および識別するための効率的な手段が用意されています。CRT デバッグ機能を使用してメモリ リークを検出する方法については、次の各トピックを参照してください。

メモリ リーク検出の有効化

メモリ リークを検出するための主要ツールは、デバッガと C ランタイム ライブラリ (CRT) デバッグ ヒープ関数です。デバッグ ヒープ関数を有効にするには、次のステートメントをプログラムに追加します。

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

メモ :

#include ステートメントは、必ずここに示した順序で記述します。順序を変更すると、関数が正しく動作しないことがあります。


crtdbg.h をインクルードすることで、malloc 関数と free 関数をそれぞれのデバッグ バージョンである _malloc_dbg と _free_dbg に対応付けます。これらが、メモリの割り当てと解放を追跡します。この対応付けは、_DEBUG が定義されているデバッグ ビルドでだけ行われます。リリース ビルドでは、通常の malloc 関数と free 関数が使用されます。

#define ステートメントにより、CRT ヒープ関数の基本バージョンがデバッグ バージョンに対応付けられます。このステートメントは必須ではありませんが、このステートメントを追加しないと、メモリ リーク情報のダンプ時に不要な情報が含まれることになります。

上記のステートメントを追加しておくと、次のステートメントをプログラムに追加することによって、メモリ リーク情報をダンプできます。

_CrtDumpMemoryLeaks();

デバッガでプログラムが実行されると、_CrtDumpMemoryLeaks? は、メモリ リーク情報を [出力] ウィンドウに表示します。次のようなメモリ リーク情報が表示されます。

Detected memory leaks!
Dumping objects ->
C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

#define _CRTDBG_MAPALLOC ステートメントを使用しなかった場合、メモリ リークのダンプは次のようになります。

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

_CRTDBG_MAP_ALLOC が定義されていない場合、次の項目が表示されます。

  • メモリ割り当て番号 (中かっこ内)
  • ブロック型 (normal、client、または CRT)
  • 16 進形式で表したメモリ位置
  • ブロックのサイズ (バイト数)
  • 最初の 16 バイトの内容 (16 進形式)

_CRTDBG_MAP_ALLOC が定義されている場合は、リークしたメモリが割り当てられたファイルの名前も表示されます。ファイル名に続くかっこ内の数字 (この例では 20) は、ファイル内の行番号です。

メモリが割り当てられたソース ファイル内の行に移動するには

  • ファイル名と行番号が表示されている [出力] ウィンドウで、目的の行をダブルクリックします。
    または
    ファイル名と行番号が表示されている [出力] ウィンドウで、目的の行を選択し、F4 キーを押します。

_CrtSetDbgFlag?

_CrtDumpMemoryLeaks? の呼び出しは、プログラムが常に同じ場所で終了するのであれば簡単です。しかし、プログラムを終了させる部分が複数存在する場合もあります。そのような場合は、終了する可能性のあるすべての場所で _CrtDumpMemoryLeaks? を呼び出すのではなく、次の呼び出しステートメントをプログラムの冒頭に追加します。

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

このステートメントは、プログラムの終了時に自動的に _CrtDumpMemoryLeaks? を呼び出します。上記のように、_CRTDBG_ALLOC_MEM_DF と _CRTDBG_LEAK_CHECK_DF の両方のビット フィールドを設定する必要があります。

CRT レポート モードの設定

前述のように、既定では _CrtDumpMemoryLeaks? は、メモリ リーク情報を [出力] ウィンドウのデバッグ ペインにダンプします。_CrtSetReportMode? を使用すると、この設定をリセットして別の場所に情報をダンプできます。また、ライブラリを使用すると、情報の出力場所が別の場所に変更されることがあります。この場合は、次のステートメントを使用して、出力先を [出力] ウィンドウに戻すことができます。

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

詳細については、「_CrtSetReportMode?」を参照してください。

メモリ ブロックの型の解釈

「メモリ リーク検出の有効化」で説明されているように、メモリ リーク情報では、リークしたメモリの各ブロックが normal ブロック、client ブロック、または CRT ブロックとして識別されます。通常、情報として表示される型は、normal ブロック型と client ブロック型だけです。

  • normal ブロックは、プログラムによって割り当てられる通常のメモリです。
  • client ブロックは、デストラクタを必要とするオブジェクト用に、MFC プログラムが使用する特殊なメモリ ブロックです。MFC の new 演算は、作成されるオブジェクトに応じて、normal ブロックまたは client ブロックを作成します。
  • CRT ブロックは、CRT ライブラリが独自に使用するために割り当てるメモリ ブロックです。これらのブロックの解放処理は CRT ライブラリが行うため、重大な問題 (CRT ライブラリの破損など) があるとき以外は、CRT ブロックがメモリ リーク レポートに表示されることはありません。

ほかにも、メモリ リーク情報には表示されないブロック型が 2 つあります。

  • free ブロックは、解放済みのメモリ ブロックです。
  • ignore ブロックは、メモリ リーク レポートに出力しないように明確にマークされているブロックです。

メモリ割り当て番号にブレークポイントを設定する


メモ :
使用している設定またはエディションによっては、表示されるダイアログ ボックスやメニュー コマンドがヘルプに記載されている内容と異なる場合があります。設定を変更するには、[ツール] メニューの [設定のインポートとエクスポート] をクリックします。詳細については、「Visual Studio の設定」を参照してください。


メモリ リーク レポートに表示されるファイル名と行番号は、リークしたメモリが割り当てられた位置を示します。しかし、メモリが割り当てられた位置を把握するだけでは、問題を十分に識別できない場合があります。プログラムの実行中に、同じ割り当てが何度も呼び出されているにもかかわらず、特定の呼び出しでだけメモリ リークが発生することも少なくありません。このような問題を識別するには、リークしたメモリが割り当てられた位置だけでなく、リークが発生する状況も把握する必要があります。そのために必要となる情報が、メモリの割り当て番号です。この番号は、レポート内のファイル名と行番号に続く中かっこ内に表示されています。たとえば、次の出力では 18 がメモリ割り当て番号です。これは、リークしたメモリが、プログラム内で割り当てられたメモリ ブロックのうち 18 番目のメモリ ブロックであることを示します。

Detected memory leaks!
Dumping objects ->
C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

CRT ライブラリは、CRT ライブラリ自身や MFC などのライブラリが割り当てるメモリも含め、プログラムの実行中に割り当てられるすべてのメモリ ブロックをカウントします。したがって、割り当て番号 N のオブジェクトは、プログラム内で N 番目にメモリが割り当てられたオブジェクトです。しかし、コード内で N 番目にメモリが割り当てられたオブジェクトであるとは限りません。むしろ、そうでない場合がほとんどです。

この割り当て番号を使用して、メモリが割り当てられた位置にブレークポイントを設定できます。このためには、プログラムの先頭付近に位置ブレークポイントを設定します。プログラムがこのポイントで停止する場合は、[クイック ウォッチ] ダイアログ ボックスまたは [ウォッチ] ウィンドウから、メモリ割り当てにブレークポイントを設定できます。

手順

[ウォッチ] ウィンドウでメモリ割り当てにブレークポイントを設定するには

1.[ウォッチ] ウィンドウの [名前] 列に、次の式を入力します。

_crtBreakAlloc

CRT ライブラリのマルチスレッド DLL バージョン (/MD オプション) を使用している場合は、次のようにコンテキスト演算子を追加します。

{,,msvcr71d.dll}_crtBreakAlloc

2.Enter キーを押します。

デバッガによって呼び出しが評価され、その結果が [値] 列に表示されます。メモリ割り当てにまだブレークポイントが設定されていない場合、この値は -1 になります。

3.[値] 列の値をブレークポイントを設定するメモリ割り当ての番号に置き換えます。たとえば、上記の出力例のメモリ割り当てでプログラムを停止させるには、18 で置換します。

目的のメモリ割り当てにブレークポイントを設定したら、デバッグを続行します。メモリ割り当ての順序が変わらないように、前回実行したときと同じ条件でプログラムを実行してください。指定したメモリ割り当てでプログラムが停止した場合、[呼び出し履歴] ウィンドウやその他のデバッガ情報を参照して、メモリが割り当てられた状況を確認できます。必要に応じて、この後も引き続きプログラムを実行してオブジェクトに何が起きるかを確認すると、オブジェクトに割り当てられたメモリが正しく解放されない原因を確認できる場合があります。


メモ :
オブジェクトにデータ ブレークポイントを設定すると役立つことがあります。詳細については、「方法 : データ ブレークポイントを設定する (ネイティブのみ)」を参照してください。


メモリ割り当てにブレークポイントを設定する場合、通常はデバッガで行う方が簡単ですが、コード内で設定することもできます。

コード内でメモリ割り当てにブレークポイントを設定するには

次のような行を追加します (18 番目のメモリ割り当てに設定する場合)。

_crtBreakAlloc = 18;

代わりに、同じ機能を果たす _CrtSetBreakAlloc? 関数を次のように使用することもできます。

_CrtSetBreakAlloc(18);

メモリ状態の比較

メモリ リークの位置を特定するためのもう 1 つの方法では、ある時点におけるアプリケーションのメモリ状態のスナップショットを取得します。CRT ライブラリには、メモリ状態のスナップショットを格納するために使用できる構造体型 _CrtMemState? が用意されています。

_CrtMemState s1, s2, s3;

任意の時点でのメモリ状態のスナップショットを取得するには、_CrtMemState? 構造体を _CrtMemCheckpoint? 関数に渡します。この関数は、現在のメモリ状態のスナップショットを構造体に格納します。

_CrtMemCheckpoint( &s1 );

_CrtMemState? 構造体を _CrtMemDumpStatistics? 関数に渡すことによって、任意の時点で構造体に格納されている内容をダンプできます。

_CrtMemDumpStatistics( &s1 );

この関数は、次のようなメモリ割り当て情報をダンプ出力します。

0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.

コード内のセクションでメモリ リークが発生したかどうかを調べるには、そのセクションの前後のメモリ状態のスナップショットを取得した後で、_CrtMemDifference? を使用して 2 つのメモリ状態を比較します。

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )
   _CrtMemDumpStatistics( &s3 );

_CrtMemDifference? という名前が示しているように、この関数は 2 つのメモリ状態 (s1 および s2) を比較し、両者の差を結果 (s3) として生成します。プログラムの先頭と末尾で _CrtMemCheckpoint? を呼び出し、_CrtMemDifference? を使用して結果を比較する方法でも、メモリ リークをチェックできます。リークが検出された場合は、_CrtMemCheckpoint? 呼び出しを使用してプログラムを分割し、バイナリ検索技法によってリークの位置を特定できます。



コメント

コメントはありません。 コメント/C言語メモ/VisualStudio/リークチェック方法?

Online: 1


FrontPage

Soft

Tips


最新の20件

2021-12-21 2020-04-06 2020-03-10 2013-06-28 2013-11-13 2014-06-24

今日の20件

  • counter: 7036
  • today: 2
  • yesterday: 1
  • online: 1

edit