構造化例外処理は、システムの異常な条件のために 処理を中断してユーザ・インターフェイスに復帰する記述を 容易にするなど様々な効果があります。
ハードウェア(CPU)の用語の『例外』とは異なるので注意してください。
キーワード:c_try, c_catch, c_end_catch, c_throw, Except3_init
Errors モジュールを使用したエラー発生(error)では、エラー終了することができても、
復旧(エラー処理)して続けて別の動作をさせることができません。
復旧させるには、ライブラリがエラーを発生する代わりに返り値でエラーが
発生したことを知らせるようにライブラリが復旧経路を作成するか、
構造化例外処理のシステムを使用するかどちらかになります。
本モジュール(Except3)は、後者の構造化例外処理システムを提供します。
復旧動作が要らないとき(システムをリセットする場合など)は、本モジュールは必要ありません。
復旧動作が必要な個所に構造化例外処理を記述します。
try ブロック(c_try) の内部(コール先を含む)で例外を投げる(c_throw) と、
catch ブロック(c_catch) にロングジャンプします。
try ブロックの外部(try を1つも記述していないときも含む)で
例外を投げると、Errors_exit 関数を呼び出してデフォルトの
エラーメッセージを出力します(参照:Errors モジュールの
「エラーメッセージの確認方法 」)
try ブロックの内部で例外が投げられなかった場合、catch ブロックに
記述されたプログラムは実行しません。
例外を投げる記述(c_throw)はモジュールの独立性を考えて
エラーハンドラの内部でのみ記述するようにします。
例外を投げると return 文のように、エラーハンドラ内の
それ以降のプログラムは実行されません。
catch ブロックの内部では、エラーが発生しないようにしてください
(詳細は「構造化例外処理の方針」を参照してください)。
C++ 言語でも使用可能です。ただし、C++ の throw を本モジュールの 例外処理ブロックの内部で発生させないようにしてください。 (ヒント:C 言語では、関数ポインタで C++ の関数を呼び出さない限り そのようなケースは起きません。C++ 言語では気を付けてください)
使用しているモジュールから発生したエラーから復帰できるように
アプリケーションを作成する手順をまとめます。
以下のソースの動きは、「構造化例外処理の動作概要」
を参照してください。
int main() { Errors_Msg msgs[5]; /* エラーメッセージ領域 */ Except3_Try trys[10]; /* try スタック領域 */ /* 構造化例外処理システムの初期化 */ Errors_MsgPool_init( Errors_MsgPool_getGlobl(), msgs, sizeof(msgs) ); Except3_init( trys, sizeof(trys) ); Errors_setErrorHandler( err ); /* 参照:エラーハンドラから例外を投げるときの記述例 */ /* メイン処理 */ c_try { sub1( "a.txt" ); sub1( "b.txt" ); } c_catch ( Errors_Msg*, msg ) { Errors_Msg_print( msg ); } c_end_catch; } void sub1( char* path ) /* 参照:構造化例外処理に対応した関数の作成 */ { FILE* file; bool bFile = false: /* file が有効かどうか */ c_try { file = fopen( path, "rt" ); if ( file == NULL ) error(); /* 参照:構造化例外処理の動作概要 */ bFile = true; : } c_finally { if ( bFile ) fclose( file ); } c_end_finally; } |
int main() { Except3_initEasy( Errors_stdErrHandler ); : | = | int main() { Errors_Msg msgs[5]; /* エラーメッセージ領域 */ Except3_Try trys[10]; /* try スタック領域 */ /* 構造化例外処理システムの初期化 */ Errors_MsgPool_init( Errors_MsgPool_getGlobl(), msgs, sizeof(msgs) ); Except3_init( trys, sizeof(trys) ); Errors_setErrorHandler( err ); : |
構造化例外処理を使用するプログラムにおけるエラーハンドラの記述例
int err( int id, int code, char* msg ) { if ( code != Errors_ASSERT && code != Except3_Err_OutOfTry ) c_throw( Errors_Msg_getGlobl() ); return ERRORS_EXIT; } |
テスト・プログラムにおけるエラーハンドラの記述例
int err( int id, int code, char* msg ) { #ifdef FOR_QUICK_TEST Errors_Msg_print( Errors_Msg_getGlobl() ); if ( code != Except3_Err_OutOfTry ) c_throw( Errors_Msg_getGlobl() ); #endif id, code, msg; /* avoid warning */ return ERRORS_EXIT; } |
通常の構造化例外処理である try ブロック 〜 catch ブロックのスタイルでなくても、 返り値のエラーコードを確認するスタイルで記述することもできます。 ただし、例外が発生したかどうかを返り値で判断できないので、本モジュールが提供する 特別な if 文を使用します。
main() { /* func 関数中に発生したエラーに対処する例 */ c_if_error { x = func(); } c_then { /* if の直後の中括弧に注意 */ printf( "error form [func()]" ); } c_endif; } |
構造化例外処理を正しくできるようにするには、関数ローカルで使用する
(FILE* をスタックに作った)ファイルを
クローズするなどの後始末処理を正しく行わなければなりません。
その場合は、finally ブロック(c_finally)を使用します。
参考:構造化例外処理の実装手順と記述例 の sub1 関数
MFC など Except3 の構造化例外処理に対応していないライブラリを 使用して、そのライブラリを超える例外を投げた場合、 ライブラリが正しく動作しないことがあります。 その場合、ライブラリの関数を呼び出す前後に、 以下の関数を使用します。(下記のサンプルを参照)
ロングジャンプしない領域で例外を投げてもロングジャンプしません。
ただし、Except3_isError 関数は true を返すようになり、
Except3_endNoJumpArea 関数を呼び出して初めてロングジャンプします。
ロングジャンプしない領域の内部に try ブロックを作成した場合、
その中でロングジャンプしますが、その外はロングジャンプしません。
try ブロックのさらにその中で
ロングジャンプしない領域を作成することもできます。
サンプル (行頭に * がついている部分は、 ロングジャンプで飛び越えません)
/* ライブラリの関数を使用する関数 */ void level1() { * Except3_startNoJumpArea(); * LibraryFunc(); /* LibraryCallback() へ */ * Except3_endNoJumpArea(); /* 例外があればここからロングジャンプします */ } /* LibraryFunc() からのコールバック関数 */ * void LibraryCallback() * { * ASSERT( Except3_isNoJumpArea() ); /* ロングジャンプしない領域かどうかチェック */ * c_try { int i = 0; c_throw(); } c_finally { final(); * } c_end_finally; * * /* 例外が投げられてもここを実行します */ * if ( Except3_isError() ) return FALSE; /* 例外があれば return FALSE する */ * else return TRUE; * } |
返り値でエラーコードを判断するプログラミング・スタイルと異なり、
例外が発生するといきなり catch ブロックにロングジャンプするので、
ユーザープログラムからエラーが発生した位置を特定することが
面倒になりますが不可能ではありません。
例外を投げる(c_throw)と exit と同様にそれ以降の(正常動作の)プログラムが
実行されないことを利用して、プログラムがどこまで実行されたかを
printf や MARK マクロ(Errors モジュール)を使用して確かめることで、
どこでエラーが発生したかを特定することができます。
関数コールスタックを表示できるデバッグ環境の場合、 エラーメッセージに含まれるエラーID を確認し、 エラーハンドラ内でエラーID を比較して ブレークするようにします。
上の2つのデバッグ方法はどちらもコンパイルする必要があるので、 サイズが大きいソースファイルをコンパイルする場合は、 非効率的になります。 また、コンパイルすることでプログラムサイズが変化し、 ハードウェアに対するタイミングが変わってしまうことがあるので、 再現性を失う可能性があります。 デバッグ時にこのようなことが起きないか心配な場合は、 バグが現れる前にあらかじめ 随時エラー確認スタイルでの構造化例外処理 を行ってください。
例外が発生したときに対処方法は、大きく2つに分れます。
エラーが発生した位置を確認するには、次の方法があります。
全ての例外を発生させることは、現実的には不可能です。
例外が発生したときのテストケースは、try ブロックの構成を参考に モジュール構成ツリーの最も深いところから例外を発生させます。
テストプログラムのエラー時に例外を投げ、エラー発生後に 次のテストを続いて行うようにすれば、 ブラックボックステスト的にテストしたことになります。