<setjmp.h>は、 C 標準ライブラリで定義されているヘッダーファイルで、「非ローカルジャンプ」、つまり通常のサブルーチン呼び出しと戻りシーケンスから逸脱する制御フローを提供します。補完的な関数とがこの機能を提供します。setjmplongjmp
の典型的な使用例setjmpは、複数の関数呼び出しレベルにわたってプログラムまたはスレッドの状態を再構築する機能を利用した例外メカニズムlongjmpの実装です。 のあまり一般的ではない使用例は、コルーチンに似た構文を作成することです。longjmpsetjmp
intsetjmp(jmp_bufenv)jmp_bufバッファを設定し、ジャンプ用に初期化します。このルーチン[ 1 ]は、引数で指定された環境バッファにプログラムの呼び出し環境を保存し、env後で使用しますlongjmp。戻り値が直接呼び出しからのものである場合、setjmp0を返します。戻り値がへの呼び出しからのものである場合longjmp、setjmp0以外の値を返します。voidlongjmp(jmp_bufenv,intvalue)envの呼び出しによって保存された環境バッファのコンテキストを、同じプログラムの呼び出しで復元します。ネストされたシグナルハンドラからの呼び出しは未定義です。 によって指定された値はからに渡されます。が完了すると、対応する の呼び出しがちょうど戻ったかのようにプログラムの実行が続行されます。に渡された が0 の場合、は 1 を返したかのように動作します。それ以外の場合は、 を返したかのように動作します。setjmplongjmpvaluelongjmpsetjmplongjmpsetjmpvaluelongjmpsetjmpvaluesetjmpプログラム実行のある時点で、現在の環境(プログラムの状態)をプラットフォーム固有のデータ構造(jmp_buf)に保存します。このデータ構造は、プログラム実行の後の時点でによって使用され、によってlongjmpに保存されたプログラムの状態を復元します。このプロセスは、環境が保存されたプログラム実行の時点への「ジャンプ」と考えることができます。からの(見かけ上の)戻り値は、制御がその時点に正常に到達したか(ゼロ)、またはへの呼び出しによって到達したか(ゼロ以外)を示します。これにより、一般的な慣用表現である:につながります。setjmpjmp_bufsetjmpsetjmplongjmpif(setjmp(x)){/* handle longjmp(x) */}
POSIX .1 は、ブロックされたシグナルの現在のセットを保存および復元するsetjmpかどうかを規定していません。プログラムがシグナル処理を使用する場合は、POSIX の/ を使用する必要があります。longjmpsigsetjmpsiglongjmp
jmp_bufC99 の理論的根拠では、後方互換性jmp_bufのための配列型であると説明されています。既存のコードは、アドレス演算子を使用せずに名前でストレージの場所を参照しますが、これは配列型でのみ可能です。 [ 2 ]また、単一のメンバーが実際のデータである単一のメンバー長の配列にすることも可能であると指摘しています。実際、これはGNU C ライブラリ で採用されているアプローチであり、このライブラリでは型を として定義しています。jmp_buf&struct __jmp_buf_tag[1]
C++でsetjmp/を介して「非ローカル goto」を実行すると、通常の「スタックアンワインド」は発生しません。そのため、必要なクリーンアップ処理も実行されません。これには、ファイルディスクリプタのクローズ、バッファのフラッシュ、ヒープに割り当てられたメモリの解放などが含まれます。longjmp
呼び出された関数が戻ると、対応するオブジェクトをsetjmp安全に使用できなくなります。これは、関数が戻るとスタック フレームが無効になるためです。 を呼び出すとスタック ポインタが復元されますが、関数が戻ったため、存在しない、または上書きまたは破損している可能性のあるスタック フレームを指すことになります。[ 3 ] [ 4 ]longjmpjmp_buflongjmp
同様に、C99 では現在のスタック フレームを保持することを要求していませんlongjmp。これは、呼び出しによって終了した関数にジャンプすることはlongjmp未定義であることを意味します。[ 5 ]
以下の例は、setjmp の基本的な考え方を示しています。ここでは、main()が を呼び出しfirst()、それがさらに を呼び出しますsecond()。その後、second()は に戻りmain()、first()の の呼び出しをスキップしますprintf()。
#include <setjmp.h> #include <stdio.h>static jmp_buf buf ;void second () { printf ( "second \n " ); // 出力longjmp ( buf , 1 ); // setjmpが呼び出された場所に戻る - setjmpは1を返す}void first () { second (); printf ( "first \n " ); // 出力されない}int main () { if ( ! setjmp ( buf )) { first (); // 実行時にsetjmpが0を返す} else { // longjmpがジャンプバックするとsetjmpが1を返すprintf ( "main \n " ); // 出力}return 0 ; }上記のプログラムを実行すると、以下の出力が得られます。
2番目のメインfirst()サブルーチンが呼び出されても、「 first」は決して出力されないことに注意してください。これは、second()制御が に戻らないためですfirst()。代わりに、main条件文がif (!setjmp(buf))2 回チェックされたときに「 」が出力されます。
この例では、他の言語とsetjmp同様に、例外処理を括弧で囲むために使用されますtry。 の呼び出しはステートメントlongjmpに類似しておりthrow、例外がエラー状態を直接 に返すことを可能にしますsetjmp。次のコードは、限られた範囲のコンテキストで を呼び出すことにより、 1999 ISO C 標準およびSingle UNIX Specificationに準拠しています。 [ 6 ]setjmp
if、switchまたは反復ステートメント!整数定数との比較と併せてこれらのルールに従うことで、実装が環境バッファを作成するのが容易になり、これはデリケートな操作となる可能性があります。[ 2 ]のより一般的な使用はsetjmp、ローカル変数の破損など、未定義の動作を引き起こす可能性があります。準拠するコンパイラと環境は、このような使用を保護したり、警告したりする必要はありません。ただし、などのやや洗練されたイディオムは文献や実務でよく見られ、比較的移植性があります。以下では、状態バッファとともに追加の変数を保持する、単純な準拠方法を示します。この変数は、バッファ自体を組み込んだ構造に展開できます。switch((exception_type=setjmp(env))){}
より現代的な例では、通常の「try」ブロックは setjmp として実装され(多段階ジャンプのための準備コードがいくつか含まれます。参照first)、"throw" は例外としてオプションのパラメータを持つ longjmp として、"catch" は "try" の下の "else" ブロックとして実装されます。
#include <setjmp.h> #include <stdio.h> #include <stdlib.h> #include <string.h>static void first (); static void second ();/* 例外スタックにはファイルスコープの静的変数を使用することで、* この翻訳単位内のどこからでもアクセスできるようになります。 */ static jmp_buf exception_env ; static int exception_type ;int main ( void ) { volatile char * mem_buffer = NULL ;if ( setjmp ( exception_env )) { // ここに到達した場合は例外が発生したprintf ( "first が失敗しました。例外タイプ: %d \n " , exception_type ); } else { // longjmp を介して失敗を通知する可能性のあるコードを実行します。puts ( "calling first" ); first ();mem_buffer = ( char * ) malloc ( 300 ); // リソースを割り当てprintf ( "%s \n " , strcpy ( mem_buffer , "first succeeded" )); // 到達しない}free ( mem_buffer ); // freeにNULLを渡しても何も操作は行われませんreturn 0 ; }static void first () { jmp_buf my_env ;puts ( "最初に入場" ); // 到達memcpy ( my_env , exception_env , sizeof my_env ); // exception_env は再利用されるため、その値を my_env に格納します。switch ( setjmp ( exception_env )) { case 3 : // ここに到達した場合は例外が発生しました。printf ( "2 回目が失敗しました。例外タイプ: 3; タイプ 1 に再マッピングします" ); exception_type = 1 ;default : // フォールスルーmemcpy ( exception_env , my_env , sizeof exception_env ); // 例外スタックを復元longjmp ( exception_env , exception_type ); // 例外処理を続行case 0 : // 通常の動作、期待される動作printf ( "calling second" ); // second ()に到達しましたprintf ( "second succeeded" ); // 到達しませんでした}memcpy ( exception_env , my_env , sizeof exception_env ); // 例外スタックを復元puts ( "leaving first" ); // 到達しない}static void second () { printf ( "entering second" ); // 到達exception_type = 3 ; longjmp ( exception_env , exception_type ); // プログラムが失敗したことを宣言するprintf ( "leaving second" ); // 到達せず}このプログラムの出力は次のとおりです。
最初の呼び出し、最初の入力、2番目の呼び出し、2番目の入力、2番目の失敗、例外タイプ: 3; タイプ 1 への再マッピング、最初の失敗、例外タイプ: 1C99 ではlongjmp、ジャンプ先が呼び出し元の関数である場合、つまりジャンプ先のスコープが損なわれていないことが保証されている場合にのみ、ジャンプが機能することが保証されています。ジャンプreturnまたは終了によって既に終了している関数へのジャンプはlongjmp未定義です。[ 5 ]longjmpただし、ジャンプを実行する際にローカル変数を明示的に破棄しない実装がほとんどです。コンテキストはローカル変数が消去されるまで存続するため、実際にはジャンプによって復元される可能性があります。多くの環境 ( Really Simple ThreadsやTinyTimberssetjmpなど) では、ジャンプなどのイディオムにより、呼び出された関数がジャンプ先で効果的に一時停止および再開できます。if(!setjmp(child_env))longjmp(caller_env);setjmp
これはスレッドライブラリによって利用され、他のファイバー機能を使用せずに協調的なマルチタスク機能を提供します。setcontext
setjmp子関数への関数は、妨害されない限り通常は機能し、POSIXの一部としてC実装で提供される必要がないことを考慮するとsetcontext、このメカニズムは代替手段が失敗した場合に移植性がある可能性がありますsetcontext。
このような仕組みでは、複数のスタックのうちいずれかがオーバーフローしても例外は発生しないため、各コンテキストに必要な領域を過大に見積もることが不可欠ですmain()。これには、通常の実行を中断する可能性のあるシグナルハンドラのための領域も含まれ、コンテキスト自体の領域も含まれます。割り当てられた領域を超えると、他のコンテキストが破損し、通常は最も外側の関数から破損します。残念ながら、このようなプログラミング戦略を必要とするシステムは、リソースが限られた小規模なシステムであることが多いのです。
#include <setjmp.h> #include <stdio.h>jmp_buf mainTask 、childTask ;void call_with_cushion (); void child ();int main () { if ( ! setjmp ( mainTask )) { call_with_cushion (); // 子プロセスは戻りません。yield します。 } // 子プロセスが最初にyieldした後、この "}" の後、実行が再開されます。while ( true ) { printf ( "Parent \n " ); if ( ! setjmp ( mainTask )) { longjmp ( childTask , 1 ); // yield - C99 では未定義であることに注意してください} } }void call_with_cushion () { char space [ 1000 ]; // main関数が実行されるのに十分なスペースを確保space [ 999 ] = 1 ; // 配列を最適化して削除しないchild (); }void child () { while ( true ) { printf ( "子ループ開始\n " ); if ( ! setjmp ( childTask )) { longjmp ( mainTask , 1 ); // yield - C99 では childTask を無効化します}printf ( "子ループ終了\n " );if ( ! setjmp ( childTask )) { longjmp ( mainTask , 1 ); // yield - C99 では childTask が無効になります} }/* 戻りません。代わりに、main() が私たちに処理を委譲しなくなることを示すフラグを設定し 、その後 longjmp(mainTask, 1) を実行します。*/ }setjmpマクロとして実装する必要があると規定されていますが、POSIX ではsetjmpマクロか関数かは未定義であると明示的に規定されています。