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したスタックフレームがなくなっているのが原因なのだが、よくわからない人は自分でスタックフレームの生成・消滅を手書きして追いかけてみるとわかると思う。














