2010年7月23日金曜日

C言語での例外処理

C言語で例外処理を書きたい。

rubyの実装で使われまくって有名になった?setjmp()とlongjmp()を使う。

そもそもの話。
例外処理がなくても、エラーが出たら、breakしたり、returnして戻っていけばいいのでは?
その答えとして、奥深い場所でエラーが起きた場合、ちまちまreturnしながら戻りますか、と答えておく。
つまり、メンドウ、というわけだ。



◆ 利用する関数
・setjmp()
jmp_buf型を引数として与えることで、
呼び出し時の環境(CPUの現在の状態であるコンテキスト)を保持する。
環境を保存できれば0を返す。
longjmp()から戻ってきた場合は、longjmp()の第2引数を返す。

・longjmp():
第2引数にint型の任意の値を入れ、setjmp()したところまで戻す。



◆ サンプルコード
#include 

jmp_buf env;

void function_A(void);
void function_B(void);
void function_C(void);


int main(int argc, char *argv[])
{
  puts("output in main()_start");
  if (setjmp(env) == 0) {
    function_A();
    puts("no output in main()");
  }
  else {
    puts("output in main()_end");
  }
  return 0;
}


void function_A(void)
{
  puts("output in function_A()");
  function_B();
  puts("not output in function_A()");
  return;
}


void function_B(void)
{
  puts("output in function_B()");
  function_C();
  puts("not output in function_B()");
  return;
}


void function_C(void)
{
  puts("output in function_C()");
  longjmp(env,1);
  puts("not output in function_C()");
  return;
}



◆ 実行
output in main()_start
output in function_A()
output in function_B()
output in function_C()
output in main()_end


例外処理のおけるtry、catch、throw句がすべて実現できている。

setjmpを呼び出すif文のtrue時。
⇒try(rubyではbegin)

setjmpを呼び出すif文のelse時。
⇒catch(rubyではrescure)

longjmp
⇒throw(rubyではraise)



ただし2点注意がある。

◆ 注意点1
setjmp()を使ったif文の外からlongjmp()を呼ぶのはまずい。

関数はスタックを使うからだ。
つまり、setjmp()を使った関数からlongjmp()を呼ぶのであれば、
それがいくら深い状態にあっても、スタックは伸びるだけだから問題ない。
しかし、setjmp()を利用した関数がreturnされれば、
スタックポインタはその関数を呼び出した元の位置に戻されてしまう。

その後、次のような処理をしていたらどうなるだろうか。
別の関数を呼び出す。

すると、setjmp()した時のスタックの状態が上書きされる可能性がある。
その関数呼び出しが終わり、スタックポインタが元に戻っても、setjpm()呼び出し時とはメモリの状態が変化している。


◆ 注意点2
もうひとつは、例外を投げたあとでもプロセスを継続させる場合だ。
C言語にはGCがないわけで、メモリを確保する処理をしていた場合は、それ破棄をしないといけない。
setjmp()した所まで一気に戻ったことでメモリリークする恐れがある。
それを心配するならreturnしながらfree()していくべきだ。


まぁ、そこまで考慮するぐらいなら別の言語を使った方がいい。