setjmp.h は、 C 標準ライブラリで定義されたヘッダーで、「非ローカル ジャンプ」を提供します。これは、通常のサブルーチン呼び出しと戻りシーケンスから逸脱する制御フローです。補完関数とがこの機能を提供します。
setjmplongjmp
setjmp/の典型的な使用法は、関数呼び出しの複数のレベルにわたってもプログラムまたはスレッドの状態を再確立する の機能を利用する例外メカニズムlongjmpの実装です。 のあまり一般的でない使用法は、コルーチンに似た構文を作成することです。
longjmpsetjmp
メンバー関数
setjmpは、プログラム実行のある時点での現在の環境 (プログラム状態) をプラットフォーム固有のデータ構造 ( jmp_buf) に保存します。このデータ構造は、プログラム実行の後の時点で によって使用され、longjmpプログラム状態を によって保存された状態に復元しますsetjmp。このプロセスは、環境を保存したjmp_bufプログラム実行ポイントへの「ジャンプ」であると想像できます。からの(見かけ上の)戻り値は、制御がそのポイントに正常に到達したか (ゼロ)、または への呼び出しから到達したか(ゼロ以外) を示します。このことから、一般的な慣用句
が生まれます。setjmpsetjmplongjmpif( setjmp(x) ){/* handle longjmp(x) */}
POSIX .1 では、ブロックされたシグナルの現在のセットを保存および復元するsetjmpかどうかは指定されていません。プログラムがシグナル処理を採用している場合は、POSIX の/ を使用する必要があります。
longjmpsigsetjmpsiglongjmp
メンバータイプ
C99 の Rationale では、は下位互換性jmp_bufのための配列型であると説明されています。既存のコードでは、記憶場所を名前で参照しますが (アドレス演算子なし)、これは配列型でのみ可能です。[2]この説明では、 は、その単一のメンバーが実際のデータである、単に 1 つのメンバー長の配列にすることができると述べられています。実際、これは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 <stdio.h> #include <setjmp.h>
静的jmp_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 " ); // 出力
0 を返す; }
上記のプログラムを実行すると、次のように出力されます。
2番 主要
first()サブルーチンが呼び出されても、は制御を に戻さないfirstため、 " " は印刷されないことに注意してください。代わりに、条件文が 2 回目にチェックされたときに " " が印刷されます。
second()first()mainif (!setjmp(buf))
例外処理
この例では、他の言語とsetjmp同様に、例外処理を囲むために が使用されていますtry。 の呼び出しは文longjmpに似ておりthrow、例外が に直接エラーステータスを返すことを可能にします。次のコードは、を限られた範囲のコンテキストで呼び出すことで、1999 ISO C 標準とSingle UNIX 仕様setjmpに準拠しています。 [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 ) { char * volatile mem_buffer = NULL ;
if ( setjmp ( exception_env )) { // ここに到達した場合、例外が発生しましたprintf ( "first failed, exception type: %d \n " , exception_type ); } else { // longjmp 経由で失敗を通知する可能性のあるコードを実行します。puts ( "calling first" ); first ();
mem_buffer = malloc ( 300 ); // リソースを割り当てるprintf ( "%s \n " , strcpy ( mem_buffer , "first successful" )); // 到達していない}
free ( mem_buffer ); // freeにNULLを渡すと、何も実行されません
0 を返す; }
静的void first (){ jmp_buf my_env ;
puts ( "最初に入力" ); // 到達
memcpy ( my_env , exception_env , sizeof my_env ); // exception_env は再利用されるため、exception_env の値を my_env に格納します
switch ( setjmp ( exception_env )) { case 3 : // ここに到達すると例外が発生しました。puts ( "2 回目が失敗しました。例外タイプ: 3; タイプ 1 に再マッピングしています" ); exception_type = 1 ;
default : // フォールスルーmemcpy ( exception_env , my_env , sizeof exception_env ); // 例外スタックを復元longjmp ( exception_env , exception_type ); // 例外処理を続行
ケース0 : // 通常、目的の操作puts ( "秒を呼び出しています" ); //秒に到達しました(); puts ( "秒に成功しました" ); // 到達していません}
memcpy ( exception_env , my_env , sizeof exception_env ); // 例外スタックを復元する
puts ( "leaving first" ); // 到達しない}
static void second () { puts ( "秒に入る" ); // 到達
exception_type = 3 ; longjmp ( exception_env , exception_type ); // プログラムが失敗したことを宣言する
puts ( "2番目を残します" ); // 到達していません}
このプログラムの出力は次のとおりです。
最初に電話する 最初に入る 2番目に呼ぶ 2番目に入る 2 回目は失敗しました。例外タイプ: 3。タイプ 1 に再マッピングします。 最初の失敗、例外タイプ: 1
協調的なマルチタスク
C99 では、 がlongjmp動作することが保証されるのは、宛先が呼び出し関数である場合、つまり宛先スコープがそのままであることが保証されている場合のみです。returnまたはによってすでに終了している関数へのジャンプはlongjmp未定義です。[5]ただし、 の実装のほとんどは、longjmpジャンプを実行するときにローカル変数を明示的に破棄しません。 コンテキストはローカル変数が消去されるまで存続するため、 によって復元される可能性がありますsetjmp。 多くの環境 (Really Simple Threads や TinyTimbers など) では、 などのイディオムによって、if(!setjmp(child_env)) longjmp(caller_env);呼び出された関数が で事実上一時停止および再開できるようになりますsetjmp。
これは、スレッド ライブラリによって利用され、またはその他のファイバー機能を使用せずに協調型マルチタスク機能を提供します。
setcontext
setjmp子関数への は、妨害されない限り通常は機能し、 はPOSIX の一部として C 実装によって提供される必要がないことを考慮するとsetcontext、このメカニズムは、代替手段が失敗した場合に移植可能である可能性がありますsetcontext。
このようなメカニズムでは、複数のスタックの 1 つがオーバーフローしても例外は生成されないため、main()通常の実行を中断する可能性のあるシグナル ハンドラーのスペースを含むコンテキストを含め、各コンテキストに必要なスペースを多めに見積もることが不可欠です。割り当てられたスペースを超えると、通常は最も外側の関数から順に、他のコンテキストが破損します。残念ながら、この種のプログラミング戦略を必要とするシステムは、リソースが限られている小規模なシステムであることが多いです。
#include <setjmp.h> #include <stdio.h>
jmp_bufメインタスク、子タスク;
void call_with_cushion (); void child ();
int main () { if ( ! setjmp ( mainTask )) { call_with_cushion (); // 子は戻らないので、yield します} // 子が最初にyieldした後、この "}" の後に実行が再開されます
while ( 1 ) { 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 ( 1 ) { printf ( "Child loop begin \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) を実行する必要があります*/ }
参考文献
- ^ ab ISO C では
setjmpマクロとして実装する必要があると規定されていますが、POSIX ではsetjmpマクロであるか関数であるかは未定義であると明示的に規定されています。 - ^ ab C99 Rationale、バージョン 5.10、2003 年 4 月、セクション 7.13
- ^ CS360 講義ノート — Setjmp と Longjmp
- ^ setjmp(3) 2009-07-26にWayback Machineでアーカイブ
- ^ ISO/IEC 9899:1999、2005、7.13.2.1:2 および脚注 211
- ^ : 非ローカル goto のジャンプ ポイントを設定する – The Open Groupの System Interfaces Reference、 The Single UNIX Specificaion、バージョン 4
さらに読む
- Nidito, Francesco (2016 年 7 月 2 日)。「Longjmp と Setjmp を使用した C の例外」。Groups.Di.Unipi.it。2024年1 月 2 日閲覧。
外部リンク
- : 非ローカル goto のジャンプ ポイントを設定する – The Open Groupの System Interfaces Reference、 The Single UNIX Specificaion、バージョン 4
- sigsetjmp/siglongjmp はありますか (再び) ( mingw / MSYSのこの関数について)
