アサーション
アサート ステートメントでは、プログラム内で true になる必要のある条件を指定します。 この条件が true にならない場合、アサーションは失敗し、プログラムの実行が中断され、[アサートに失敗しました] ダイアログ ボックスが表示されます。
Visual Studio は、次の構造に基づく C++ のアサート ステートメントをサポートしています。
MFC アサーション (MFC プログラムの場合)
ATLASSERT (ATL を使用するプログラムの場合)
CRT アサーション (C ランタイム ライブラリを使用するプログラムの場合)
ANSI assert 関数 (その他の C/C++ プログラムの場合)
アサーションを使用して論理エラーを検出し、操作の結果を確認することで、処理する必要があるエラー条件をテストできます。
このトピックの内容
アサーションのしくみ
MFC ライブラリまたは C ランタイム ライブラリのアサーションによってデバッガーが停止したとき、ソース ファイルが利用可能である場合は、そのソース ファイル内のアサーションが発生した位置にジャンプします。 アサーション メッセージが [出力] ウィンドウと [アサートに失敗しました] ダイアログ ボックスの両方に表示されます。 後で参照するためにアサーション メッセージを保存する場合は、[出力] ウィンドウからテキスト ウィンドウへコピーします。 [出力] ウィンドウに、他のエラー メッセージが表示されていることもあります。 アサーションが失敗した原因を突き止める手掛かりとなるため、これらのエラー メッセージをよく調べてください。
開発中にアサーションを使用してエラーを検出します。 一般的に、想定ごとに 1 つのアサーションを使用します。 たとえば、引数が NULL でないと想定した場合、アサーションを使用してその想定が正しいかどうかをテストします。
デバッグ ビルドとリリース ビルドでのアサーション
アサート ステートメントは、_DEBUG
が定義されている場合のみコンパイルされます。 定義されていない場合、コンパイラはアサーションを null ステートメントとして扱います。 したがって、プログラムの最終リリース バージョンではアサート ステートメントのオーバーヘッドやパフォーマンス コストはゼロになります。#ifdef
ディレクティブを使用する必要もありません。
アサーション使用の副作用
アサーションをコードに追加するときは、そのアサーションに副作用がないことを確認してください。 たとえば、nM
値を変更する次のアサーションを考えます。
ASSERT(nM++ > 0); // Don't do this!
ASSERT
式はプログラムのリリース バージョンでは評価されないため、nM
の値は、デバッグ バージョンとリリース バージョンとでは異なります。 MFC でこの問題を回避するには、ASSERT
ではなく VERIFY マクロを使用します。 VERIFY
によってすべてのバージョンの式が評価されますが、リリース バージョンの結果はチェックされません。
アサート ステートメントで関数を呼び出す場合は、その関数の評価によって予想外の副作用が生じることがあるため特に注意してください。
ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe
VERIFY
は、デバッグ バージョンでもリリース バージョンでも myFnctn
を呼び出すため、安全に使用できます。 ただし、VERIFY
を使用すると、リリース バージョンで不要な関数呼び出しのオーバーヘッドが発生します。
CRT アサーション
CRTDBG.H
ヘッダー ファイルは、アサーションによるチェックを行うための _ASSERT
および _ASSERTE
マクロを定義します。
マクロ | 結果 |
---|---|
_ASSERT |
指定した式が FALSE と評価された場合、_ASSERT 対象のファイル名と行番号を出力します。 |
_ASSERTE |
_ASSERT と同様の結果と共に、アサートされた式の文字列形式も出力します。 |
FALSE と評価されたアサート対象の式もレポートするため、_ASSERTE
マクロの方が強力です。 このマクロを使用すると、ソース コードを参照しなくても問題を特定できることがあります。 ただし、アプリケーションのデバッグ バージョンに、_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 を返し、_CrtDbgBreak
は DebugBreak
を使用してデバッガーを呼び出します。
すべてのアサーションを一時的に無効にする必要がある場合は、_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
マクロの引数がゼロまたは false に評価された場合、マクロはプログラムの実行を停止し、ユーザーに警告を表示します。それ以外の場合、プログラムの実行を続けます。
アサーションが失敗すると、アサーション対象のソース ファイル名と行番号がメッセージ ダイアログ ボックスに表示されます。 このダイアログ ボックスで [再試行] を選択すると、AfxDebugBreak が呼び出され、デバッガーのためにプログラムの実行が中断されます。 この時点で、呼び出し履歴を調べるかその他のデバッガー機能を使って、アサーションが失敗した原因を確認できます。 Just-In-Time デバッグを有効にしておくと、デバッガーがまだ実行されていない場合に、このダイアログ ボックスからデバッガーを起動できます。
ASSERT
を使用して関数の戻り値をチェックする方法を次に示します。
int x = SomeFunc(y);
ASSERT(x >= 0); // Assertion fails if x is negative
ASSERT を次のように IsKindOf 関数と組み合わせて使用すると、関数の引数の型チェックを行うことができます。
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
をオーバーライドしている場合)。
たとえば、CMyData
クラスのメンバー変数の 1 つに CObList が格納されているとします。 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 ...
};
AssertValid
クラスでオーバーライドした CMyData
は次のようになります。
#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
マクロを呼び出します。
CObList
クラスも AssertValid
をオーバーライドしているため、有効性の検証はこの段階では停止しません。 このオーバーライドにより、そのクラスが表すリストの内部状態の有効性も検証されます。 つまり、CMyData
オブジェクトの有効性を検証すると、そのメンバー変数に格納されている CObList
リスト オブジェクトの内部状態の有効性も検証されることになります。
さらにコードを追加すると、リストに格納されている CPerson
オブジェクトの有効性も検証できます。 たとえば、CPersonList
から派生クラス CObList
を生成し、AssertValid
をオーバーライドできます。 このオーバーライドでは、CObject::AssertValid
を呼び出した後、リストに格納されている AssertValid
オブジェクトごとに CPerson
を呼び出すことによってリストを反復処理します。 CPerson
クラスは、このトピックの冒頭に示したように、既に AssertValid
をオーバーライドしています。
これらの関数やマクロは、デバッグ バージョンのビルドに強力な機構を提供します。 リリース バージョンのビルド時には、この機構は自動的にオフになります。
AssertValid に関する制限事項
この関数は、オブジェクトが不正な場合に、アサートしてプログラムの実行を停止します。 ただし、アサートされない場合でも、問題が検出されなかったことを示すだけで、必ずしもオブジェクトに問題がないと保証されたわけではありません。
アサーションの使用
論理エラーの検出
プログラムの論理に従えば true になるはずの条件にアサーションを設定しておきます。 論理エラーが発生しない場合、このアサーションは特に機能しません。
たとえば、容器内の気体分子をシミュレートすると想定し、変数 numMols
で分子の総数を表すことにします。 この数値が 0 より小さくなることはあり得ないため、次のような MFC アサート ステートメントを挿入しておきます。
ASSERT(numMols >= 0);
または、次のように CRT アサーションを挿入することもできます。
_ASSERT(numMols >= 0);
プログラムが正しく動作していれば、これらのステートメントは何の処理も行いません。 ただし、論理エラーにより numMols
がゼロ未満になると、アサーションによってプログラムの実行が停止され、[アサートに失敗しました] ダイアログ ボックスが表示されます。
結果の確認
アサーションは、ざっと見ただけでは結果がわかりにくい演算をテストする場合に有用です。
たとえば、次のコードを検討してみます。このコードは、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
以下であることが必要です。 このループを目で見ただけでは必ずそうなるかどうかを確認できないため、ループの後にアサート ステートメントを挿入し、その条件が満たされているかどうかをチェックします。
処理されないエラーの検索
アサーションを使用すると、コード内のエラー処理が完了しているはずの個所で、エラー条件をチェックできます。 次の例のグラフィック ルーチンは、エラー コードを返します。または、正常終了した場合は 0 を返します。
myErr = myGraphRoutine(a, b);
/* Code to handle errors and
reset myErr if successful */
ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version
エラー処理コードが正しく機能していれば、アサート ステートメントに達する前に、発生したエラーは処理され、myErr
は 0 にリセットされるはずです。 myErr
が別の値である場合、アサーションは失敗し、プログラムは停止し、[アサートに失敗しました] ダイアログ ボックスが表示されます。
ただし、アサート ステートメントは、エラー処理コードに代わるものではありません。 最終リリース バージョンのコードで問題となる可能性のあるアサート ステートメントの例を次に示します。
myErr = myGraphRoutine(a, b);
/* No Code to handle errors */
ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!
このコードは、アサート ステートメントを使用してエラー条件を処理しています。 その結果、myGraphRoutine
が返すエラー コードは、最終リリース バージョンのコードでは処理されないままになります。