Plan 9 ソースコードでの例外機構 使い方編
Plan 9のソースコードを読んでいると以下のような関数が頻繁に出てきます.
- error()
- waserror()
- poperror()
- nexterror()
これらはC言語で例外をサポートするためにPlan 9で利用される関数です.
具体的には以下のように使います
1 int func(){ 2 if( waserror() ){ 3 recover_from_error(); 4 return ERROR; 5 } 6 7 // below could err 8 if( !do_something_has_error() ){ 9 error("failed!"); 10 11 } 12 13 poperror(); 14 return SUCCESS; 15 }
recover_from_error()とdo_something_has_error()はコードを書く人が挿入した任意の処理だと考えてください. recover_from_error()はたとえば取得したリソースを解放したりと言ったことを行います. do_something_has_error()は仮に処理が上手くいかなかったら0を返す関数としましょう.
このときこのfunc()はどのようなフローになるでしょうか?waserror()は通常0を返す事になっています.このため最初のif節はスルーされることになります.
そうして8行目が実行されるわけですが, 仮にここでdo_something_has_error()が何らかの処理の結果, 0(失敗)を返したとします. 当然のことながら, if節が実行されその中でerror()が実行されますね.
この時, error()が呼び出されたらなんと次の瞬間には"直前の"waserror()の行まで戻る,という流れになっているのです. そしてこの時はwaserror()は1を返すようになります.このため, 先ほどはスルーされたif節が実行される事になるのです.
逆に以下のように書くこともできます.
1 int func(){ 2 if( !waserror() ){ 3 // below could err 4 if( !do_something_has_error() ) 5 error("failed!"); 6 poperror(); 7 return SUCCESS; 8 } 9 10 recover_from_error(); 11 12 return ERROR; 13 }
この場合, エラーが無ければ3-7行目が実行されますが, do_something_has_error()が0を返すと,
5行目の次に2行目に飛びます. このときwaserror()は1を返すので, if節の処理は実行されずに10行目のrecover_from_error()が実行されることになります.
共にRubyで類似コードを書くと以下のようになるでしょうか
1 def func() 2 if( !do_someghint_has_error() ) 3 raise "failed!" 4 end 5 return SUCCESS; 6 rescue 7 recover_from_error(); 8 return ERROR; 9 end
これらの例外機構のもっともオソロシイ使い方はnexterror(), poperror()を併用したコード上での逆戻りです.nexterror()はerror()同様に"直前の"waserror()に飛ぶ関数であり, poperror()は例外を一つ消すものだと考えてください.
これらを用いると以下のような関数がかけます.
1 int func(){ 2 a = alloc(sizeof(a)); 3 if( waserror() ){ 4 free(a); 5 nexterror(); 6 } 7 8 // do something 9 10 b = alloc(sizeof(b)); 11 if( waserror() ){ 12 free(b); 13 nexterror(); 14 } 15 16 c = alloc(sizeof(c)); 17 if( waserror() ){ 18 free(c); 19 nexterror(); 20 } 21 22 if( !do_something_has_error() ){ 23 error("we go back"); 24 } 25 26 poperror(); 27 poperror(); 28 poperror(); 29 return SUCCESS; 30 }
正常に実行されていれば, 2→10→16→22→26行目というように処理が運びます.
一方で, 22行目で先ほどと同じように"エラー"とされerror()関数が呼び出された場合, 直前のwaserror()である17行目まで戻ります. その中でnexterror()が呼ばれていますが, これはerror()関数と同じく直前のwaserror()の行までジャンプします.
このため最終的な結果として, do_something_has_error()が"エラー"を起こすと, 18行目のfree(c), 12行目のfree(b), 4行目のfree(a)が順々に呼び出されることになります.
そして最後(コード上は最初)のwaserror()を含むif文ではさらにnexterror()を呼び出しています
3 if( waserror() ){ 4 free(a); 5 nexterror(); 6 }
これはさらにその外, func()を呼び出しているような関数に対して例外を投げています.
このためたとえばfunc()を呼び出す関数do_func()は以下のようになっている必要があります.
1 int do_func(){ 2 if( !waserror() ){ 3 func(); 4 poperror(); 5 return SUCCESS; 6 } 7 8 // tell something 9 return ERROR; 10 }