C++教科書/標準ライブラリ編/<csetjmp>の章

編集

概要

編集

<csetjmp>ヘッダーは、プログラム実行中のコンテキスト(CPU registers、スタックポインタなど)を保存し、後から復元できる機能を提供します。この機能は「非局所脱出(non-local exit)」と呼ばれ、例外処理や delimcc(プログラミング言語の制御構造)の実装などに利用されます。

jmp_buf型は、実行コンテキストを格納するためのオブジェクト型です。その実装は環境に依存しますが、通常はCPUレジスタやスタックポインタなどを表す構造体として定義されています。

マクロ

編集

setjmp(env)マクロは、現在の実行コンテキストをenv(jmp_buf型の変数)に保存します。戻り値は次のようになります。

0
setjmp呼び出し時の値
非0
longjmp呼び出し時に渡された値

volatile修飾子は、setjmpの呼び出し箇所でコンパイラの最適化を抑制するために必要です。最適化により、setjmpの前後のコードが入れ替わったり削除されると、意図しない動作になる可能性があるためです。

関数

編集

longjmp(env, val)関数は、envで指定された実行コンテキストに復元(ジャンプ)し、val(0以外の値)を呼び出し元のsetjmpの戻り値として返します。longjmpから復帰した場合、プログラムは異常終了します。

使用例

編集
#include <csetjmp>
#include <csignal>
#include <cstdio>

jmp_buf env;

void signal_handler(int /*sig*/) {
    longjmp(env, 1);  // ジャンプ先に1を渡す
}

auto main() -> int {
    std::signal(SIGINT, signal_handler);  // Ctrl+Cで割り込みシグナル

    volatile int const ret = setjmp(env);  // setjmpの結果によって分岐
    if (ret == 0) {
        std::puts("Normal execution");
    } else {
        std::puts("Jumped from signal handler");
    }

    // 以下の処理は実行されない
    return 0;
}

この例では、Ctrl+Cが入力されるとシグナルハンドラが呼ばれ、longjmpでmain関数の先頭に戻ります。setjmpの戻り値が0以外なので、"Jumped from signal handler"と出力されます。

実装上の注意点

編集
  • シグナルハンドラ内でlongjmpを呼び出す場合、シグナルマスクや自動変数などの状態が復元される保証がないため注意が必要です。
  • マルチスレッドプログラミングにおいて、スレッド間で実行コンテキストを共有すると不整合が発生するおそれがあります。
  • 最適化の影響を避けるため、setjmpの呼び出し箇所でvolatile修飾子を付ける必要があります。
  • longjmpの呼び出し先は、setjmpを呼び出したスタック上の位置と同じ関数内に限られます。関数の外へジャンプすると動作は未定義です。

まとめ

編集

<csetjmp>ヘッダーは非局所脱出のための機能を提供しますが、プログラムの動作が複雑になるため、 適切な使用に注意が必要です。例外処理などの他の機能で置き換えられる場合が多いでしょう。