JavaやC++などのようなプログラミング言語では,try〜catchのような例外処理機構が提供されていますが,残念ながらC言語ではそのような構文はありません.そのかわりに,プログラマは局所的な(すなわち,関数内部におけるネストしたfor文などからの)脱出にはgotoを使ったり,また,深い関数呼出しからの脱出には,setjmp/longjmpを使ったりします.ここでは,Cのマクロ機能を駆使して,Javaのようなtry〜catch〜finally機構を実現する方法を紹介します.
伝統的なC言語プログラミングでは,エラー処理は例えば以下のように関数の戻り値をチェックすることによって実現されます.
ret = foo(a1, ...); if (ret < 0) { // エラー処理 }
このような方式はシンプルで良いのですが,いくつか問題もあります.一つは,返り値の範囲の一部がエラーコードとして予約されてしまうこと.また,関数の呼出しが深くなった場合に,いちいち返り値をチェックする必要があることも問題です.たとえば,整数値を文字列として持つようなノードから構成される2分木構造のデータがあったとして,それらの合計値を求める関数を考えます.エラー処理をまったく考えない場合,以下のように非常にシンプルな再帰的関数として実現することができます.
int treesum(node_t *n) { if (n == NULL) return 0; return atoi(n->val) + treesum(n->left) + treesum(n->right); }
さて,実際にはデータが不完全で,ノードnのメンバーvalがNULLポインタになってるケースがあるとします.この場合,treesumは計算を途中で終了してエラーコード(-1)を返さなくてはなりません.ところが,もともとのtreesumはツリーの各ノードの合計値を返す仕様になっていたので,かわりに合計値を引数で渡されたポインタの先に書き込んでやるようにする必要があります.そして, treesumの呼出しごとに返り値のエラーコードをチェックするようにします.結果として以下のようなコードになるでしょう.
int treesum(node_t *n, int *sum) { if (n == NULL) return 0; if (n->val == NULL) return -1; *sum += atoi(n->val); if (treesum(n->left, sum) < 0) return -1; if (treesum(n->right, sum) < 0) return -1; }
treesumの呼出し側では,合計値を受けとるための変数のアドレスを渡してやり,エラーコードをチェックします.
int sum; if (treesum(root, &sum) < 0) { // エラー処理 }
例外が使えるとしたら,どう書けるでしょう.
int treesum(node_t *n) { if (n == NULL) return 0; if (n->val == NULL) throw "exception: value is NULL"; return atoi(n->val) + treesum(n->left) + treesum(n->right); } for (...) { for (...) { ... if (error happened) goto L_ERROR; ... } } L_ERROR: // do something stuff
一方,JavaやC++などでは,try-catchのような例外処理の機構が用意されています.これによって,gotoやsetjmp/longjmpなどを使わなくても,統一的な機構で例外フローを実現できます.