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