setjmp/longjmpをつかった初歩的な例。
#include <stdio.h> #include <setjmp.h> jmp_buf env; void hoge() { longjmp(env, 1); } int main() { int x = 0; volatile int y = 0; int e; if ((e = setjmp(env)) != 0) { /* error */ printf("x=%d\n", x); printf("y=%d\n", y); return 1; } x = 1; y = 1; hoge(); return 0; }
これを実行してみると:
% gcc -O -Wall -Wextra foo.c % ./a.out x=0 y=1 %
つまりvolatileをつけている変数はlongjmpしてもlongjmp前の値になっているのに対し、volatileなしの変数はsetjmp時の値になっている。gcc -Sでアセンブラを出力させてみる。
.LC0: .string "x=%d\n" .LC1: .string "y=%d\n" .text .p2align 4,,15 .globl main .type main, @function main: .LFB4: subq $24, %rsp .LCFI1: movl $0, 20(%rsp) y=0。xはレジスタに割り当てられたようだ。 movl $env, %edi call setjmp testl %eax, %eax setjmp()==0だったら je .L4 .L4のところにジャーンプ movl $0, %esi printfの引数は0で決め打ち。 movl $.LC0, %edi "x=%d\n" movl $0, %eax call printf movl 20(%rsp), %esi yの値を引数レジスタにセット movl $.LC1, %edi "y=%d\n" movl $0, %eax call printf movl $1, %eax jmp .L6 .p2align 4,,7 .L4: movl $1, 20(%rsp) y=1 movl $0, %eax call hoge movl $0, %eax .L6: addq $24, %rsp ret .LFE4: .size main, .-main
こんな感じで変数がレジスタに割り当てられているとlongjmpで戻ってきた時にすべてのレジスタがsetjmp時の値に巻き戻されるので変数の値も戻ってしまう。なのでlongjmpで戻ってきたあとに参照したい変数(エラー理由が入ってる変数とか)はvolatileをつけておかないとダメ。
ま、C++だとデストラクタがあるのでlongjmpは使えないし、かわりにtry/throw/catchつかえってことなんだけども。これならコンパイラがローカル変数の書き換えを追いかけられるので変数をレジスタに割り当てても大丈夫でいちいちvolatileにしなくてもいいわけで。
あと、setjmpをwrapしたmysetjmpをつくるとうまくいかないので注意。
#include <stdio.h> #include <setjmp.h> jmp_buf env; int mysetjmp(jmp_buf env) { return setjmp(env); } void hoge() { printf("HOGE\n"); longjmp(env, 1); } int main() { volatile int y = 0; int e; if ((e = mysetjmp(env)) != 0) { printf("ERROR\n"); printf("y=%d\n", y); return 1; } y = 1; hoge(); return 0; }
% gcc -O -Wall -Wextra bar.c % ./a.out HOGE %
理由はlongjmpするときにはsetjmpしたスタックフレームがなくなっているのが原因なのだが、よくわからない人は自分でスタックフレームの生成・消滅を手書きして追いかけてみるとわかると思う。