[迷信] setjmp マクロの返却値は変数に代入できる

setjmp マクロは C の標準ライブラリの一部ですが、知らない方も少なくないでしょうし、知っていても使ったことがない方も多いでしょうから、今回は本題に入る前に、少し setjmp の解説から行うことにします。

setjmp というのは、<setjmp.h> ヘッダで定義されるマクロで、同じヘッダで宣言される longjmp と組み合わせて使用します。C++ の例外と同じように、関数の枠組みを飛び越えて、呼び出し元の関数の特定の場所へジャンプすることができます。

#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 マクロの実行を終えたかのように振舞います。

setjmplongjmp の組み合わせを使って、ちょっと凝ったことをしようと思うと、setjmp マクロの返却値をいったん変数に代入(あるいは初期化)したいと考えるのは当然のことです。

int cause = setjmp(env);

のようにです。しかし、上記のような使い方は C の規格上許されません。この制約を課す記述を JIS X3010:2003 から引用すると、

環境限界 setjmp マクロの呼出しは,次に示すいずれかの文脈にしか現れてはならない。

  • 選択文又は繰返し文の制御式全体
  • 他方のオペランドが整数定数式である関係演算子又は等価演算子の一方のオペランド。この場合,関係演算し又は等価演算しによる式は,選択文又は繰返し文の制御式全体でなければならない。
  • 単項 ! 演算子のオペランド。この場合,単項 ! 演算子による式は,選択文又は繰返し文の制御式全体でなければならない。
  • 式文の式全体(void にキャストされていてもよい。)。

 setjmp マクロの呼出しがこれ以外の文脈に現れた場合,その動作は,未定義とする。

すなわち、宣言の中に現れる(他の変数を初期化する)ことも、代入演算子のオペランドになる(他の変数に代入する)ことも、未定義の動作を引き起こすことになるわけです。

したがって、setjmp マクロの返却値によって処理を振り分ける唯一の方法は switch 文ということになります。どうしても変数に代入したい場合は、jmp_buf 型の変数とあわせて別の変数をもう一つ管理し、その変数を setjmp の返却値の代わりに使う必要があります。