アサーション
アサート ステートメントは、プログラムの時点で真であると想定する条件を指定します。この条件が true でない場合、アサーションは失敗し、プログラムの実行が停止し、アサーションに失敗しました]ダイアログ ボックス が表示されます。
Visual C++ は、次の構成に基づくアサート ステートメントをサポートします:
MFC プログラムの MFC アサーション。
ATLASSERT (ATL を使用するプログラムの場合)
CRT アサーション (C ランタイム ライブラリを使用するプログラムの場合)
ANSI assert 関数 (その他の C/C++ プログラムの場合)
およびテスト エラー状態チェックするロジックを、エラーの結果を処理する必要がある操作の特定にアサーションを使用できます。
このトピックの内容
アサーションのしくみ
デバッグ ビルドとリリース ビルドのアサーション
アサーションの副作用
CRT アサーション
MFC アサーション
MFC ASSERT_VALID と CObject::AssertValid
AssertValid に関する制限事項
アサーションを使用する
論理エラーのキャッチ
結果のチェック
ハンドルされないエラーを検索
アサーションのしくみ
デバッガーが MFC には、または 15 B のランタイム ライブラリ アサーション停止すると、ソースが使用できる場合、デバッガーはアサーションが発生したソース ファイルの時点に移動します。アサーション メッセージは [出力ウィンドウ] と [アサートに失敗しました] のダイアログ ボックスの両方に表示されます。後で参照するためにアサーション メッセージを保存する場合は、[出力] ウィンドウからテキスト ウィンドウへコピーします。[出力] ウィンドウに、他のエラー メッセージが表示されていることもあります。アサーションが失敗した原因を突き止める手掛かりとなるため、これらのエラー メッセージをよく調べてください。
開発中のエラーを検出するには、アサーションを使用します。一般に、各コンポーネントのデータには、1 とおりのアサーションを使用します。引数が null でないと仮定した場合、たとえばこの仮定をテストするには、アサーションを使用します。
このトピックの内容
デバッグ ビルドとリリース ビルドのアサーション
アサート ステートメントは _DEBUG が定義されている場合のみコンパイルされます。それ以外の場合、コンパイラは null ステートメントとしてアサーションを処理します。したがって、アサート ステートメントは、プログラムの最終リリースやパフォーマンスのオーバーヘッド コストを強制しません。また、#ifdef ディレクティブの使用を避けることができます。
アサーションの副作用
アサーションをコードに追加するときは、そのアサーションに副作用がないことを確認してください。たとえば、nM の値を変更する次のアサーションを検討する:
ASSERT(nM++ > 0); // Don't do this!
ASSERT の式がプログラムのリリース バージョンで評価されないため、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 マクロが定義されています。
マクロ |
結果 |
---|---|
_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によってデバッガーを呼び出します。
ヒープ破損のチェック
次の例は、_CrtCheckMemory を使用してヒープの破損をチェックします。
_ASSERTE(_CrtCheckMemory());
ポインターの有効性チェック
次の例は、_CrtIsValidPointer を使用して、指定したメモリ範囲への読み書きが有効かどうかを検証します。
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
次の例は、_CrtIsValidHeapPointer を使用して、ポインターがローカル ヒープ上のメモリを指しているかどうかを検証します。ここでのローカル ヒープとは、C ランタイム ライブラリのこのインスタンスによって作成および管理されるヒープを指します。DLL はライブラリの独自のインスタンスを持っているため、アプリケーション ヒープの外部に独自のヒープを所有していることになります。このアサーションは、null アドレスや範囲外のアドレスだけでなく、静的変数、スタック変数、その他の非ローカル メモリを指すポインターも検出します。
_ASSERTE(_CrtIsValidPointer( myData );
メモリ ブロックのチェック
次の例は、_CrtIsMemoryBlock を使用して、メモリ ブロックがローカル ヒープ上にあり、有効なブロック型を持つかどうかを検証します。
_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));
このトピックの内容
MFC アサーション
MFC には、アサーションによるチェックを行うための ASSERT マクロが定義されています。また、CObject派生オブジェクトの内部状態をチェックするための MFC ASSERT_VALID と CObject::AssertValid のメソッドを定義します。
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 メソッドを使用すると、オブジェクトの内部状態を実行時にチェックできます。CObject からクラスを派生させる場合、必ずしも AssertValid をオーバーライドする必要はありませんが、そうすることによって、クラスの信頼性を高めることができます。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 ...
};
CMyData クラスでオーバーライドした AssertValid は次のようになります。
#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
// Call inherited AssertValid.
CObject::AssertValid( );
// Check validity of CMyData members.
ASSERT_VALID( m_pDataList );
// ...
}
#endif
CMyData クラスは、AssertValid の機構を使用して、データ メンバーに格納されているオブジェクトの有効性を検証します。CMyData クラスでオーバーライドした AssertValid は、独自の m_pDataList メンバー変数に対して ASSERT_VALID マクロを呼び出します。
有効性のテストは、クラス CObList も AssertValidをオーバーライドするため、このレベルでは停止しません。このオーバーライドにより、そのクラスが表すリストの内部状態の有効性も検証されます。つまり、CMyData オブジェクトの有効性を検証すると、そのメンバー変数に格納されている CObList リスト オブジェクトの内部状態の有効性も検証されることになります。
さらにコードを追加すると、リストに格納されている CPerson オブジェクトの有効性も検証できます。たとえば、CObList から派生クラス CPersonList を生成し、AssertValid をオーバーライドできます。このオーバーライドでは、CObject::AssertValid を呼び出した後、リストに格納されている CPerson オブジェクトごとに AssertValid を呼び出すことによってリストを反復処理します。CPerson クラスは、このトピックの冒頭に示したように、既に AssertValid をオーバーライドしています。
これらの関数やマクロは、デバッグ バージョンのビルドに強力な機構を提供します。リリース バージョンのビルド時には、この機構は自動的にオフになります。
AssertValid に関する制限事項
この関数は、オブジェクトが不正な場合に、アサートしてプログラムの実行を停止します。ただし、アサートされない場合でも、問題が検出が、オブジェクトに問題が保証されることだけを示しています。
このトピックの内容
アサーションを使用する
論理エラーのキャッチ
プログラムの論理に従えば true になるはずの条件にアサーションを設定しておきます。論理エラーが発生しない場合、このアサーションは特に機能しません。
たとえば、容器内の気体分子をシミュレートすると想定し、変数 numMols で分子の総数を表すことにします。この数値が 0 より小さくなることはあり得ないため、次のような MFC アサート ステートメントを挿入しておきます。
ASSERT(numMols >= 0);
または、次のように CRT アサーションを挿入することもできます。
_ASSERT(numMols >= 0);
プログラムが正しく動作していれば、これらのステートメントは何の処理も行いません。ただし、論理エラーにより numMolsを未満であるためには、アサートこれらのステートメントは、プログラムの実行を停止し、[アサートに失敗しました] ダイアログ ボックスを表示します。
このトピックの内容
結果のチェック
アサーションは、結果が視覚的に、マニフェストからテストの操作のために役立ちます。
たとえば、次のコードを検討してみます。このコードは、mols の指すリンク リストの内容に基づいて変数 iMols を更新します。
/* 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 が返すエラー コードは、最終リリース バージョンのコードでは処理されないままになります。
このトピックの内容