| C標準ライブラリ(libc) |
|---|
| 一般的なトピック |
| その他のヘッダー |
<setjmp.h>は、 C標準ライブラリで定義されたヘッダーで、「非局所ジャンプ」、つまり通常のサブルーチン呼び出しと戻り値のシーケンスから逸脱する制御フローを提供します。この機能は、補完関数とによって提供されます。 setjmplongjmp
setjmp/の典型的な用途は、複数レベルの関数呼び出しにまたがってプログラムまたはスレッドの状態を再構築するの能力を活用する例外メカニズムlongjmpの実装です。 / のあまり一般的ではない用途としては、コルーチンに似た構文を作成することが挙げられます。 longjmpsetjmp
メンバー関数
intsetjmp(jmp_bufenv)- ローカル
jmp_bufバッファを設定し、ジャンプのために初期化します。このルーチン[ 1 ]は、プログラムの呼び出し環境を引数で指定された環境バッファに保存し、env後で が使用できるようにしますlongjmp。直接呼び出しからの戻り値の場合はsetjmp0を返します。 の呼び出しからの戻り値の場合longjmpはsetjmp0以外の値を返します。 voidlongjmp(jmp_bufenv,intvalue)- プログラムの同じ呼び出しでルーチン[ 1 ]
envの呼び出しによって保存された環境バッファのコンテキストを復元します。ネストされたシグナルハンドラからの呼び出しはundefinedです。 で指定された値はからに渡されます。が完了した後、プログラムの実行は、対応する の呼び出しがちょうど戻ったかのように続行されます。に渡された が0の場合、は1を返したかのように動作します。それ以外の場合は、 を返したかのように動作します。setjmplongjmpvaluelongjmpsetjmplongjmpsetjmpvaluelongjmpsetjmpvalue
setjmpは、プログラム実行のある時点での現在の環境(プログラム状態)をプラットフォーム固有のデータ構造(jmp_buf)に保存します。このデータ構造は、プログラム実行の後の時点で によって使用され、longjmpプログラム状態を によって保存された状態に復元しますsetjmp。このプロセスは、環境を保存したjmp_bufプログラム実行ポイントへの「ジャンプ」であると想像できます。 からの(見かけ上の)戻り値は、制御がそのポイントに正常に到達したか(ゼロ)、 への呼び出しから到達したか(ゼロ以外)を示します。このことから、一般的な慣用句が生まれます。setjmpsetjmplongjmpif(setjmp(x)){/* handle longjmp(x) */}
POSIX .1 では、ブロックされたシグナルの現在のセットを保存および復元するsetjmpかどうかは指定されていません。プログラムがシグナル処理を採用している場合は、POSIX の/ を使用する必要があります。 longjmpsigsetjmpsiglongjmp
メンバーの種類
jmp_buf- 呼び出し環境を復元するために必要な情報を保持するのに適した配列型。
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 <setjmp.h> #include <stdio.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、例外が に直接エラーステータスを返すことを可能にしますsetjmp。以下のコードは、を限定された範囲のコンテキストで呼び出すことで、 1999年のISO C標準およびSingle UNIX仕様に準拠しています。 [ 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 failed, exception type: %d \n " , exception_type ); } else { // longjmp 経由で失敗を通知する可能性のあるコードを実行します。puts ( "calling first" ); first ();mem_buffer = ( char * ) 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 : // ここに到達すると例外が発生しました。printf ( "2 回目が失敗しました。例外タイプ: 3; タイプ 1 に再マッピングしています" ); exception_type = 1 ;default : // フォールスルーmemcpy ( exception_env , my_env , sizeof exception_env ); // 例外スタックを復元longjmp ( exception_env , exception_type ); // 例外処理を続行ケース0 : // 通常、目的の操作printf ( "second を呼び出しています" ); // secondに到達しました(); printf ( "second に成功しました" ); // 到達していません}memcpy ( exception_env , my_env , sizeof exception_env ); // 例外スタックを復元するputs ( "最初に退出する" ); // 到達しない}static void second () { printf ( "秒に入る" ); // 到達exception_type = 3 ; longjmp ( exception_env , exception_type ); // プログラムが失敗したことを宣言するprintf ( "2番目を残します" ); // 到達していません}このプログラムの出力は次のとおりです。
最初に呼び出し、最初に入力、2 番目を呼び出し、2 番目に入力、2 番目が失敗しました。例外タイプ: 3。最初にタイプ 1 への再マッピングが失敗しました。例外タイプ: 1協調型マルチタスク
C99 では、 が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メインタスク、子タスク;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 ); // 結果 - これは C99 では未定義であることに注意してください} } }void call_with_cushion () { char space [ 1000 ]; // main を実行するのに十分なスペースを予約するspace [ 999 ] = 1 ; // 配列が存在しなくなるまで最適化しないchild (); }void child () { while ( true ) { 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) を実行する必要があります。 */ }参考文献
- ^ a b ISO C では、
setjmpマクロとして実装する必要があると規定されていますが、POSIX では、setjmpマクロであるか関数であるかは未定義であると明示的に規定されています。 - ^ a b C99 Rationale、バージョン5.10、2003年4月、セクション7.13
- ^ CS360 講義ノート — Setjmp と Longjmp
- ^ setjmp(3) 2009年7月26日アーカイブWayback Machine
- ^ a b ISO/IEC 9899:1999、2005、7.13.2.1 :2および脚注211
- ^ : 非ローカル goto のジャンプポイントを設定する – The Open Groupの System Interfaces Reference、 The Single UNIX Specification、バージョン 5
さらに読む
- 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 Specification、バージョン 5
- sigsetjmp/siglongjmp はありますか (再び) ( mingw / MSYSのこの関数について)