setjmpマクロはC言語の標準ライブラリの一部ですが、知らない方も少なくないでしょうし、知っていても使ったことがない方も多いと思います。今回は本題に入る前に少しsetjmpの解説から行うことにします。
setjmpというのは、<setjmp.h>ヘッダで定義されるマクロで、同じヘッダで宣言されるlongjmp関数と組み合わせて使用します。C++の例外と同じように、関数の枠組みを飛び越えて呼び出し元の関数の特定の場所へ分岐することができます。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <setjmp.h> jmp_buf env; void f(void) { longjmp(env, 1); } void g(void) { if (setjmp(env) == 0) { /* 最初はこちらのブロックが実行される */ f(); } else { /* f 関数内の longjmp が実行されると、こちらのブロックが実行される */ } } |
setjmpマクロを実行すると、その時点での情報をjmp_buf型の引数(上記の例ではenv)に保存します。そして、setjmpマクロは必ず0を返します。
その後、longjmp関数がsetjmpマクロで状態を保存したjmp_buf型の引数を伴って呼び出されると、setjmpマクロを呼び出した場所に制御が移ります。そして、longjmp関数の第2引数として与えた値を返却値として、あたかもsetjmpマクロの実行を終えたかのように振舞います。
setjmpマクロとlongjmp関数の組み合わせを使って、ちょっと凝ったことをしようと思うと、setjmpマクロの返却値をいったん変数に代入(あるいは初期化)したいと考えるのは当然のことです。
0 1 2 |
int cause = setjmp(env); |
のようにです。しかし、上記のような使い方は C の規格上許されません。この制約を課す記述を JIS X3010:2003 から引用すると、
環境限界 setjmpマクロの呼出しは,次に示すいずれかの文脈にしか現れてはならない。
- 選択文又は繰返し文の制御式全体。
- |他方のオペランドが整数定数式である関係演算子又は等価演算子の一方のオペランド。この場合,関係演算子又は等価演算子による式は,選択文又は繰返し文の制御式全体でなければならない。
- 単項!演算子のオペランド。この場合,単項!演算子による式は,選択文又は繰返し文の制御式全体でなければならない。
- 式文の式全体(voidにキャストされていてもよい。)。
setjmpマクロの呼出しがこれ以外の文脈に現れた場合,その動作は,未定義とする。
すなわち、宣言の中に現れる(他の変数を初期化する)ことも、代入演算子のオペランドになる(他の変数に代入する)ことも、未定義の動作を引き起こすことになるわけです。
したがって、setjmpマクロの返却値によって処理を振り分ける唯一の方法はswitch文ということになります。どうしても変数に代入したい場合は、jmp_buf型の変数とあわせて別の変数をもう一つ管理し、その変数をsetjmpマクロの返却値の代わりに使う必要があります。