次の方法で共有


C/C++ アサーション

アサーション ステートメントは、プログラム内の特定の時点で true であると予想される条件を指定します。 その条件が true でない場合、アサーションは失敗し、プログラムの実行が中断され 、[アサーションに失敗しました] ダイアログ ボックス が表示されます。

Visual Studio では、次のコンストラクトに基づく C++ アサーション ステートメントがサポートされています。

  • MFC プログラム用のMFCアサーション機能。

  • ATL を使用するプログラム向けの ATLASSERT

  • C ランタイム ライブラリを使用するプログラムの CRT アサーション。

  • 他の C/C++ プログラムの ANSI アサート関数

    アサーションを使用して、ロジック エラーのキャッチ、操作の結果の確認、処理する必要があるエラー条件のテストを行うことができます。

このトピックの内容

アサーションのしくみ

デバッグ ビルドとリリース ビルドにおけるアサーション(宣言)

アサーションを使用した場合の副作用

CRT アサーション

MFC アサーション

アサーションのしくみ

MFC または C ランタイム ライブラリ アサーションが原因でデバッガーが停止すると、ソースが使用可能な場合、デバッガーはアサーションが発生したソース ファイル内のポイントに移動します。 アサーション メッセージは 、[出力] ウィンドウ と [アサーションに 失敗しました ] ダイアログ ボックスの両方に表示されます。 将来参照できるように保存する場合は、[ 出力] ウィンドウからテキスト ウィンドウにアサーション メッセージをコピーできます。 [出力] ウィンドウには、他のエラー メッセージも含まれている場合があります。 これらのメッセージは、アサーションエラーの原因の手がかりを提供するため、注意深く調べてください。

アサーションを使用して、開発中のエラーを検出します。 原則として、前提条件ごとに 1 つのアサーションを使用します。 たとえば、引数が NULL でないと仮定した場合は、アサーションを使用してその仮定をテストします。

このトピックの内容

デバッグおよびリリースビルドにおけるアサーション

アサーション ステートメントは、 _DEBUG が定義されている場合にのみコンパイルされます。 それ以外の場合、コンパイラはアサーションを null ステートメントとして扱います。 したがって、アサーション ステートメントでは、最終的なリリース プログラムでオーバーヘッドやパフォーマンス コストが発生しないため、 #ifdef ディレクティブの使用を回避できます。

アサーションを使用した場合の副作用

コードにアサーションを追加するときは、アサーションに副作用がないことを確認します。 たとえば、 nM 値を変更する次のアサーションを考えてみましょう。

ASSERT(nM++ > 0); // Don't do this!

ASSERT式はプログラムの Release バージョンでは評価されないため、nMのデバッグ バージョンとリリース バージョンでは異なる値が使用されます。 MFC でこの問題を回避するには、の代わりに ASSERT マクロを使用します。 VERIFY は、すべてのバージョンの式を評価しますが、リリース バージョンの結果は確認しません。

アサーション ステートメントで関数呼び出しを使用する場合は特に注意してください。これは、関数を評価すると予期しない副作用が発生する可能性があるためです。

ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe

VERIFY はデバッグ バージョンとリリース バージョンの両方で myFnctn を呼び出すので、使用できます。 ただし、 VERIFY を使用すると、リリース バージョンで不要な関数呼び出しのオーバーヘッドが発生します。

このトピックの内容

CRT アサーション

CRTDBG.H ヘッダー ファイルは、アサーション チェック用の_ASSERTマクロと_ASSERTE マクロを定義します。

Macro 結果
_ASSERT 指定した式が FALSE に評価された場合、 _ASSERTのファイル名と行番号。
_ASSERTE _ASSERTと同じで、アサートされた式の文字列表現を加えたもの。

_ASSERTE は、FALSE であると判明したアサート式を報告するため、より強力な機能を持っています。 これは、ソース コードを参照せずに問題を特定するのに十分な場合があります。 ただし、アプリケーションのデバッグ バージョンには、 _ASSERTEを使用してアサートされた各式の文字列定数が含まれます。 多くの _ASSERTE マクロを使用する場合、これらの文字列式は大量のメモリを占有します。 それが問題であることが証明された場合は、 _ASSERT を使用してメモリを節約します。

_DEBUGが定義されている場合、_ASSERTE マクロは次のように定義されます。

#define _ASSERTE(expr) \
    do { \
        if (!(expr) && (1 == _CrtDbgReport( \
            _CRT_ASSERT, __FILE__, __LINE__, #expr))) \
            _CrtDbgBreak(); \
    } while (0)

アサートされた式が FALSE に評価された場合、 アサーション エラーを報告するために_CrtDbgReportが呼び出されます (既定ではメッセージ ダイアログ ボックスを使用)。 メッセージ ダイアログ ボックスで [再試行 ] を選択した場合、 _CrtDbgReport は 1 を返し、 _CrtDbgBreakDebugBreakを介してデバッガーを呼び出します。

すべてのアサーションを一時的に無効にする必要がある場合は、 _CtrSetReportModeを使用します。

ヒープ破損の確認

次の例では 、_CrtCheckMemory を使用してヒープの破損を確認します。

_ASSERTE(_CrtCheckMemory());

ポインターの有効性の確認

次の例では 、_CrtIsValidPointer を使用して、特定のメモリ範囲が読み取りまたは書き込みに有効であることを確認します。

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

次の例では 、_CrtIsValidHeapPointer を使用して、ポインターがローカル ヒープ内のメモリをポイントすることを確認します (C ランタイム ライブラリのこのインスタンスによって作成および管理されるヒープ— DLL はライブラリの独自のインスタンスを持つ可能性があるため、アプリケーション ヒープの外部で独自のヒープを持つことができます)。 このアサーションは、null または範囲外のアドレスだけでなく、静的変数、スタック変数、およびその他の非ローカル メモリへのポインターもキャッチします。

_ASSERTE(_CrtIsValidHeapPointer( myData );

メモリ ブロックの確認

次の例では 、_CrtIsMemoryBlock を使用して、メモリ ブロックがローカル ヒープにあり、有効なブロックの種類があることを確認します。

_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));

このトピックの内容

MFC アサーション

MFC は、アサーション チェック用の ASSERT マクロを定義します。 また、MFC ASSERT_VALID派生オブジェクトの内部状態をチェックするためのCObject::AssertValidメソッドとCObjectメソッドも定義します。

MFC ASSERT マクロの引数が 0 または false に評価された場合、マクロはプログラムの実行を停止し、ユーザーに警告します。それ以外の場合は、実行が続行されます。

アサーションが失敗すると、メッセージ ダイアログ ボックスにソース ファイルの名前とアサーションの行番号が表示されます。 ダイアログ ボックスで [再試行] を選択した場合、 AfxDebugBreak を呼び出すと、実行がデバッガーに中断されます。 その時点で、呼び出し履歴を調べ、他のデバッガー機能を使用して、アサーションが失敗した理由を特定できます。 Just-In-Time デバッグを有効にしていて、デバッガーがまだ実行されていない場合は、ダイアログ ボックスでデバッガーを起動できます。

次の例は、 ASSERT を使用して関数の戻り値を確認する方法を示しています。

int x = SomeFunc(y);
ASSERT(x >= 0);   //  Assertion fails if x is negative

IsKindOf 関数で ASSERT を使用して、関数引数の型チェックを提供できます。

ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );

ASSERT マクロは、リリース バージョンではコードを生成しません。 リリース バージョンで式を評価する必要がある場合は、ASSERT ではなく VERIFY マクロを使用します。

MFC ASSERT_VALIDとCObject::AssertValid

CObject::AssertValid メソッドは、オブジェクトの内部状態の実行時チェックを提供します。 AssertValidからクラスを派生するときにCObjectをオーバーライドする必要はありませんが、これを行うことでクラスの信頼性を高めることができます。 AssertValid は、オブジェクトのすべてのメンバー変数に対してアサーションを実行して、有効な値が含まれていることを確認する必要があります。 たとえば、ポインター メンバー変数が NULL ではないことを確認する必要があります。

次の例は、 AssertValid 関数を宣言する方法を示しています。

class CPerson : public CObject
{
protected:
    CString m_strName;
    float   m_salary;
public:
#ifdef _DEBUG
    // Override
    virtual void AssertValid() const;
#endif
    // ...
};

AssertValidをオーバーライドする場合は、独自のチェックを実行する前に、基底クラスバージョンのAssertValidを呼び出します。 次に、ASSERT マクロを使用して、次に示すように、派生クラスに固有のメンバーを確認します。

#ifdef _DEBUG
void CPerson::AssertValid() const
{
    // Call inherited AssertValid first.
    CObject::AssertValid();

    // Check CPerson members...
    // Must have a name.
    ASSERT( !m_strName.IsEmpty());
    // Must have an income.
    ASSERT( m_salary > 0 );
}
#endif

メンバー変数のいずれかがオブジェクトを格納している場合は、 ASSERT_VALID マクロを使用して内部の有効性をテストできます (クラスが AssertValidオーバーライドする場合)。

たとえば、メンバー変数の 1 つに CMyData を格納するクラス について考えてみます。 CObList変数m_DataListは、CPerson オブジェクトのコレクションを格納します。 CMyDataの省略された宣言は次のようになります。

class CMyData : public CObject
{
    // Constructor and other members ...
    protected:
        CObList* m_pDataList;
    // Other declarations ...
    public:
#ifdef _DEBUG
        // Override:
        virtual void AssertValid( ) const;
#endif
    // And so on ...
};

AssertValidCMyDataオーバーライドは次のようになります。

#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
    // Call inherited AssertValid.
    CObject::AssertValid( );
    // Check validity of CMyData members.
    ASSERT_VALID( m_pDataList );
    // ...
}
#endif

CMyData では、 AssertValid メカニズムを使用して、データ メンバーに格納されているオブジェクトの有効性をテストします。 AssertValidのオーバーライドされたCMyDataは、独自のm_pDataListメンバー変数に対してASSERT_VALID マクロを呼び出します。

クラス CObListAssertValidもオーバーライドするため、有効性テストはこのレベルでは停止しません。 このオーバーライドは、リストの内部状態に対して追加の有効性テストを実行します。 したがって、 CMyData オブジェクトの有効性テストは、格納されている CObList リスト オブジェクトの内部状態に対する追加の有効性テストにつながります。

さらに作業を行う場合は、リストに格納されている CPerson オブジェクトの有効性テストを追加することもできます。 CPersonListからクラス CObListを派生させ、AssertValidをオーバーライドすることができます。 オーバーライドでは、CObject::AssertValidを呼び出し、リストを反復処理し、リストに格納されている各AssertValid オブジェクトに対してCPersonを呼び出します。 このトピックの冒頭に示す CPerson クラスは、既に AssertValidをオーバーライドしています。

これは、デバッグ用にビルドするときの強力なメカニズムです。 その後リリース用にビルドすると、メカニズムは自動的にオフになります。

AssertValid の制限事項

トリガーされたアサーションは、オブジェクトが間違いなく無効であり、実行が停止することを示します。 しかし、アサーションがない場合は、問題が見つからなかったことを示すだけですが、オブジェクトが良好であることは保証されません。

このトピックの内容

アサーションの使用

ロジック エラーの検出

プログラムのロジックに従い、適用される条件が真であることを確かめるために、アサーションを設定できます。 ロジック エラーが発生しない限り、アサーションは無効です。

たとえば、コンテナー内のガス分子をシミュレートしていて、変数 numMols が分子の合計数を表す場合です。 この数値を 0 より小さくすることはできません。そのため、次のような MFC アサーション ステートメントを含めることができます。

ASSERT(numMols >= 0);

または、次のような CRT アサーションを含めることができます。

_ASSERT(numMols >= 0);

プログラムが正しく動作している場合、これらのステートメントは何も行いません。 ただし、ロジック エラーによって numMols が 0 未満になった場合、アサーションはプログラムの実行を停止し 、[アサーションに失敗しました] ダイアログ ボックスを表示します。

このトピックの内容

結果の確認

アサーションは、ぱっと見ただけでは結果が明らかでない操作をテストする場合に有用です。

たとえば、iMolsが指すリンク リストの内容に基づいて変数molsを更新する次のコードを考えてみます。

/* This code assumes that type has overloaded the != operator
 with const char *
It also assumes that H2O is somewhere in that linked list.
Otherwise we'll get an access violation... */
while (mols->type != "H2O")
{
    iMols += mols->num;
    mols = mols->next;
}
ASSERT(iMols<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version

iMolsでカウントされる分子の数は、常に分子の総数以下である必要があります(numMols)。 ループの視覚的検査では、これが必ずしも当てはまるとは限らないので、ループの後にアサーション ステートメントを使用してその条件をテストします。

このトピックの内容

ハンドルされていないエラーを見つける

アサーションを使用すると、コード内でエラーが処理される必要がある時点でエラー状態をテストできます。 次の例では、グラフィック ルーチンは成功のためにエラー コードまたはゼロを返します。

myErr = myGraphRoutine(a, b);

/* Code to handle errors and
   reset myErr if successful */

ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version

エラー処理コードが正常に動作する場合は、アサーションに到達する前にエラーを処理し、 myErr ゼロにリセットする必要があります。 myErrに別の値がある場合、アサーションは失敗し、プログラムは停止し、[アサーションに失敗しました] ダイアログ ボックスが表示されます。

ただし、アサーション ステートメントはエラー処理コードに代わるものではありません。 次の例は、最終的なリリース コードで問題が発生する可能性があるアサーション ステートメントを示しています。

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!

このコードは、エラー条件を処理するためにアサーション ステートメントに依存します。 その結果、 myGraphRoutine によって返されたエラー コードは、最終的なリリース コードでは処理されません。

このトピックの内容