エラー発生機能は、続きの処理が実行できないときに使うものなので、 不正な値をとったときのすべてのケースでエラー発生機能を使うこと (例外を発生させること)がベストであるとは限りません。
表示しようとしているデータが不正な値になっているときは、 エラーを発生するのではなく、エラーであることを表示します。 エラー表示したら続いて次のデータを表示することができます。
エラーは、ASSERT マクロや error マクロによって発生します。
エラーを発生させた場合、デフォルトではプログラムを強制終了します。
ただし、Errors モジュールがプログラムを強制終了する前に
実行するエラーハンドラを登録すれば、
プログラム終了時の動作をプログラミングすることもできます。
マクロ名 | 内容 |
---|---|
ASSERT | デバッグ版のみのチェック(省略形) |
ASSERT2_0 | デバッグ版のみのチェック(完全形) |
error | エラー発生(省略形) |
error2_0 | エラー発生(完全形) |
error_clear | エラークリア |
エラーメッセージの記憶領域がタスクごとに用意されているなど、 本機能はマルチタスク環境でも正しく動作します。 ただし、マルチタスク環境では、あるタスクにエラーが発生しても、 システムの一部はまだ動いているので、エラーハンドラの処理内容に 注意が必要です。
エラーメッセージを確認するには、次の方法があります。
プログラムの開発工数の削減のため、エラーメッセージが ソースファイル名と行番号のみになることがよくあります (error 関数または ASSERT を使用)。 この場合、ソースファイルを参照してヒントを得てください。 ただし、よく発生するエラーに関しては、詳細なエラーメッセージを 表示するようにしていますので、要望があれば開発者に相談してください (error2_0 マクロや ASSERT2_0 等を使用)。
エラーが発生した位置を確認するには、いくつかの方法があります。
Visual C++ 開発環境でコンソール(コマンドライン)アプリケーションを デバッグ実行してエラーが発生したとき、 エラーメッセージを表示してすぐに コマンドプロンプト・ウィンドウが消えてしまいます。
ウィンドウが消えないようにしてエラーメッセージを確認するには、 errors.c のプログラム終了ポイント([EXIT_POINT] で検索) にブレークポイントを張ってください。
エラーハンドラは、エラー発生機能を使用してエラーが発生したときに、
呼び出される関数です。
エラーハンドラ(関数)はアプリケーションプログラマが作成し、
main 関数のはじめの方(エラーが発生する前)で
登録する関数を呼び出すようにします(Errors_setErrorHandler マクロ)。
デバッグするときは、エラーハンドラにブレークポイントを貼っておくと良いでしょう。
int err( int id, int code, char* msg ) { #ifndef NDEBUG if ( code == Errors_ASSERT ) Errors_break( 0xEEEE ); #endif if ( code != Errors_ASSERT && code != Except3_Err_OutOfTry ) c_throw( Errors_Msg_getGlobl() ); #ifdef NDEBUG /* 下の2行のうちどちらか */ Errors_printf_release( "%s", msg ); Errors_MsgPool_print( Errors_MsgPool_getGlobl() ); #endif return ERRORS_EXIT; } |
エラーハンドラを登録するには、Errors_setErrorHandler マクロを使用します。 メイン関数の最初で実行してください。 登録できる関数は1つだけなので、2回以上 Errors_setErrorHandler マクロを 呼び出すときは、注意してください。また、マルチタスク環境でも 登録できる関数は1つだけなので、どのタスクにエラーが発生しても 同じエラーハンドラが呼び出されるので注意してください。 複数のエラーハンドラを登録して呼び出したいときは、 その機能を持った関数をエラーハンドラ内で呼び出してください。
エラーハンドラは、次のような形式になっています。
int err( int id, int code, char* msg ); |
エラーコードとエラーメッセージには、エラー発生機能で指定された
ものが入っています。ですので、エラーの内容を知る場合、
それぞれのモジュールのエラーコードを確認するか、
エラーメッセージを表示させてください。
ただし、エラーメッセージは容量を食うので、Errors モジュールの
設定によっては内容が入っていないこともあります。
引数 id は、エラーが起きるたびに +1(初期値は1)する値が
代入されています。これにより、デバッグ時に無視したいエラーと
無視したくないエラーを迅速に判断できるようになります。
また、他人が発生させたエラーと同じエラーであることを
確認する手段の1つにもなります。
返り値は、次の定数を指定します。
ERRORS_EXIT を指定した場合、Errors_errPrintf によるエラーメッセージの 出力と、後始末チェックを行います。
エラーハンドラで処理する内容は、次のようなものが考えられます。
エラーの内容を通知するには、Errors_errPrintf を使用することが できます。独自の通知方法を使ったり、デバッガのウォッチ機能を 使っても構いません。
エラー発生機能を使用してエラーが発生したら、基本的にプログラムを
終了する(exit)かリセットするしかありません。
しかし、ソフトウェア例外処理(Except3)を使用することで、
エラーから復旧することもできます。
詳細は、例外処理のドキュメント中の「例外処理の実装手順と記述例」の項を
参照してください。
Except3 などを使用して例外を発生させた場合、 エラーハンドラからリターン(return 文を使用) しなくても構いません。 try ブロックの外へ例外メッセージが投げられたときは、 エラーコードが Except3_Err_OutOfTry で 再びエラーハンドラが呼び出されます。 そのときは、返り値を ERRORS_EXIT にするなどして プログラムを強制終了するようにしてください。
デバッグは次のようにすると、素早く行うことができます。
ただ単に、データを表示するだけなら WD 等のマクロを使用すれば済みますが、 Errors_printf を用いて、 データの意味を含めて整形された表示を行うようにすれば、 データの意味を考えることが少なくなり、 デバッグを速やかに行うことができるようになります。
整形された表示を行うには、整形して表示するための工数がかかりますが、
オブジェクト(構造体)ごとにそのデータを表示するような関数を
作成すれば、流用が効くようになります。
たとえば、Staff 構造体があれば、Staff_print という関数を作成します。
struct Staff { char name[80]; int age; Staff* partner; }; void Staff_print( Staff* this ) { Errors_printf( "Staff(%p)[%s]: age=%d, partner=(%p)%d", this, this->name, this->age, this->partner, this->partner->name ); } |
表示する内容に、構造体名と構造体のアドレスを含むようにすると
デバッグ時に便利です。
上のような表示関数は、デバッグ用なので ERRORS_CUT_DEBUG_TOOL で
リリース時にカットできるようにしておきます。
ある特定の関数のデバッグの頻度が大きい場合は、 その関数の内部にデバッグ表示関数を埋めこみ、 #if ERRORS_DEBUG_FALSE または #if ERRORS_DEBUG_TRUE で囲むようにすると、 表示非表示を簡単に切替えることができるようになります。
関数コール履歴は、スタックに積まれた関数コールの様子を記録しています。 コールスタックを備えていないデバッガで、コールスタックと同じことを 行います。
既に関数コール履歴に対応しているモジュールを使用しているときに、 コールスタックを見たいときは、次のように記述します。
関数コール履歴に対応した関数にするには、以下の記述が必要になります。
int func() { int local; ERRORS_FUNC_START( func ); /* 関数の始まり */ local = sub(); local *= 2; ERRORS_FUNC_END( func ); /* 関数の終わり */ return local; } |
ERRORS_FUNCLOG_PRINT_EVERY マクロを定義すると、
関数に出入りするたびに Errors_printf による処理中の関数の表示を行います。
ただ、そのまま表示しようとすると、デバッグ表示がとても多くなるので、
関数コール履歴表示の洗練を行います。
ただし、関数に入ったかどうかをトレースできなくなるので、
注目している関数がトレースされるか注意してください。
ERRORS_FUNCLOG_SPECIFIC マクロを定義すると、
特定のソースファイルだけ関数コール履歴を有効にします。
関数コール履歴を有効にするソースファイルの先頭に、
以下のような #include 文を記述します。
(errors_fs.h は、errors.h のあるフォルダと同じところにあります)
#if ERRORS_DEBUG_TRUE #include "C:\mo\02\COMPONE\TODA\src\errors_fs.h" #endif |
関数コール履歴表示の洗練を行う場合、 ERRORS_FUNC_START2 〜 ERRORS_FUNC_END2 を用いて、 レベル指定を行います。 以下のキーワードを ERRORS_FUNC_START2 〜 ERRORS_FUNC_END2 の第1引数に指定します。 上にあるものほど制限がきついときでも表示されます。
#define ERRORS_FUNC_MAIN_INF 2 /* 特に表示するインターフェイス */ #define ERRORS_FUNC_MAIN_IMP 3 /* 特に表示するインプリメント */ #define ERRORS_FUNC_INF 4 /* レベル指定を行わなかったときのデフォルト */ #define ERRORS_FUNC_IMP 5 #define ERRORS_FUNC_CRITICAL_INF 6 #define ERRORS_FUNC_CRITICAL_IMP 7 /* 表示を制限するインプリメント */ ERRORS_FUNC_START2( ERRORS_FUNC_INF, func ); /* 関数の始まり */ ERRORS_FUNC_END2( ERRORS_FUNC_INF, func ); /* 関数の終わり */ |
C++ 言語では、メンバ変数の関数ポインタを取得できないので、以下のように ダミー変数を用意します。
int CDlg::OnInitDialog() { ERRORS_FUNC_CPP_VAR( CMixerDlg_OnInitDialog ); /* ダミー変数宣言 */ ERRORS_FUNC_START_CPP( CMixerDlg_OnInitDialog ); ERRORS_FUNC_END_CPP( CMixerDlg_OnInitDialog ); return FALSE; } |
ERRORS_FUNC_START と ERRORS_FUNC_END は、関数の始まりと終わりを 示しているだけで、そのマクロを使用したからといって、 errors モジュールを必ず使用しなければならないことはありません。 これらのマクロを上書きしたり、無効にしたりして対応できるからです。
C++ では、グローバル変数のデストラクタは exit 関数の呼び出しの 後で呼び出されます。このため、本モジュールの Errors_exit 関数による デフォルトのテストツールが各種後始末関数を呼び出す前に実行されて しまいます。これを避けるには #define ERRORS_NO_EXIT_CHK を 設定してください。
デストラクタ中でエラーが発生した場合、Errors_exit 関数に再び入り、 デストラクタが2度起動してしまうので、 以下のように2度目は後始末処理をしないようにしてください。
CSampleApp::~CSampleApp() { static int count = 0; count++; if ( count == 1 ) { Xxx_finish( m_Xxx ); ERRORS_CHK_DEFAULT(); } } |
exit によってプログラムが終了したときに再開する OS が 無いときは、アセンブラで exit というラベルの ダミー関数が必要になります。 スタートアップ・ルーチン内のプログラム終了処理の先頭に exit ラベルをつけておくと良いでしょう。 デバッガ上でプログラム実行するときは、exit ラベルに ブレークポイントをつけるといいでしょう。
コンパイラや OS が提供するスタートアップ・ルーチンを 変更したくないときは、halt 命令をアセンブラで任意の場所に記述し、 exit というラベルを付けます。 exit ラベルは公開(globl)してください。
プログラムが強制終了したときの動作は次のようになります。
デバッガによっては、上記の命令を検出できないでハングアップしたような状態になる 場合があるので注意してください。