setjmp()
組み込みプログラミングでlongjmp()
関数を実際に使用できる場所を正確に説明できる人はいますか? これらはエラー処理用であることを知っています。しかし、いくつかのユースケースを知りたいです。
8 に答える
エラー処理
他の多くの関数にネストされた関数の奥深くにエラーがあり、エラー処理は最上位関数でのみ意味があるとします。
その間のすべての関数が正常に戻り、戻り値またはグローバルエラー変数を評価して、それ以上の処理が意味をなさないか、さらには悪いことでさえあると判断する必要があるとしたら、非常に面倒で面倒です。
これは setjmp/longjmp が理にかなっている状況です。これらの状況は、他の言語 (C++、Java) での例外が理にかなっている状況に似ています。
コルーチン
エラー処理の他に、C で setjmp/longjmp が必要な別の状況も考えられます。
コルーチンを実装する必要がある場合です。
ここに小さなデモの例があります。Sivaprasad Palas からのサンプル コードの要求を満たし、setjmp/longjmp がコルーチンの実装をどのようにサポートするかという TheBlastOne の質問に答えてくれることを願っています (非標準または新しい動作に基づいていないことがわかる限り)。
編集:コールスタックをダウンさ
せるのは、
実際には未定義の動作である可能性があります(MikeMB のコメントを参照してください。まだ確認する機会はありません)。longjmp
#include <stdio.h>
#include <setjmp.h>
jmp_buf bufferA, bufferB;
void routineB(); // forward declaration
void routineA()
{
int r ;
printf("(A1)\n");
r = setjmp(bufferA);
if (r == 0) routineB();
printf("(A2) r=%d\n",r);
r = setjmp(bufferA);
if (r == 0) longjmp(bufferB, 20001);
printf("(A3) r=%d\n",r);
r = setjmp(bufferA);
if (r == 0) longjmp(bufferB, 20002);
printf("(A4) r=%d\n",r);
}
void routineB()
{
int r;
printf("(B1)\n");
r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10001);
printf("(B2) r=%d\n", r);
r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10002);
printf("(B3) r=%d\n", r);
r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10003);
}
int main(int argc, char **argv)
{
routineA();
return 0;
}
実行の流れを次の図に示します。
注意事項
setjmp/longjmp を使用する場合は、考慮されていないローカル変数の有効性に影響を与えることに注意してください。
参照。このトピックに関する私の質問。
理論的には、それらをエラー処理に使用できるため、チェーン内のすべての関数でエラーを処理する必要なく、深くネストされた呼び出しチェーンから飛び出すことができます。
すべての巧妙な理論と同様に、これは現実に直面すると崩壊します。中間関数は、メモリを割り当て、ロックを取得し、ファイルを開き、クリーンアップが必要なあらゆる種類のことを行います。したがって、実際にはsetjmp
/longjmp
は、環境を完全に制御できる非常に限られた状況 (一部の組み込みプラットフォーム) を除いて、通常は悪い考えです。
setjmp
私の経験では、ほとんどの場合、 /を使用するとうまくいくと思うときはいつでもlongjmp
、プログラムは明確で単純であり、呼び出しチェーンのすべての中間関数呼び出しでエラー処理を行うことができexit
ます。エラーが発生します。
setjmp
との組み合わせlongjmp
は「超強力goto
」。細心の注意を払って使用してください。ただし、他の人が説明したように、18層の関数のエラーメッセージを少しずつ戻す必要があるのではなく、すばやくしlongjmp
たいときに、厄介なエラー状況から抜け出すのに非常に役立ちます。get me back to the beginning
ただし、 と同じgoto
ですが、さらに悪いことに、これの使用方法には本当に注意する必要があります。Alongjmp
は、コードの先頭に戻るだけです。setjmp
と の間で変更された可能性のある他のすべての状態には影響しませんsetjmp
。setjmp
したがって、割り当て、ロック、半分初期化されたデータ構造などは、呼び出された場所に戻ったときにまだ割り当てられ、ロックされ、半分初期化されています。これは、これを行う場所に本当に気を配る必要があることを意味します。これ以上のlongjmp
問題を引き起こさずに呼び出すことが本当に問題ないということです。もちろん、次に行うのが [おそらくエラーに関するメッセージを保存した後] の「再起動」である場合、たとえば組み込みシステムで、ハードウェアが悪い状態にあることを発見した場合は問題ありません。
また、非常に基本的なスレッド化メカニズムを提供するのを見setjmp
たり、使用したりしました。longjmp
しかし、これは非常に特殊なケースです。「標準」スレッドがどのように機能するかは間違いありません。
編集: もちろん、C++ がコンパイルされたコードに例外ポイントを格納し、何が例外を発生させ、何がクリーンアップを必要とするかを知るのと同じ方法で、「クリーンアップに対処する」コードを追加できます。これには、ある種の関数ポインタ テーブルが含まれ、「ここから下に飛び出す場合は、この引数を使用してこの関数を呼び出します」という格納が必要になります。このようなもの:
struct
{
void (*destructor)(void *ptr);
};
void LockForceUnlock(void *vlock)
{
LOCK* lock = vlock;
}
LOCK func_lock;
void func()
{
ref = add_destructor(LockForceUnlock, mylock);
Lock(func_lock)
...
func2(); // May call longjmp.
Unlock(func_lock);
remove_destructor(ref);
}
このシステムを使えば、「C++のような完全な例外処理」を行うことができます。しかし、これは非常に厄介で、適切に記述されたコードに依存しています。
setjmp
単体テストに非常にlongjmp
役立ちます。
次のモジュールをテストしたいとします。
#include <stdlib.h>
int my_div(int x, int y)
{
if (y==0) exit(2);
return x/y;
}
通常、テストする関数が別の関数を呼び出す場合は、特定のフローをテストするために実際の関数が行うことを模倣する呼び出し用のスタブ関数を宣言できます。ただし、この場合、関数はexit
戻りません。スタブは、何らかの方法でこの動作をエミュレートする必要があります。 setjmp
そしてlongjmp
あなたのためにそれを行うことができます。
この関数をテストするために、次のテスト プログラムを作成できます。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>
// redefine assert to set a boolean flag
#ifdef assert
#undef assert
#endif
#define assert(x) (rslt = rslt && (x))
// the function to test
int my_div(int x, int y);
// main result return code used by redefined assert
static int rslt;
// variables controling stub functions
static int expected_code;
static int should_exit;
static jmp_buf jump_env;
// test suite main variables
static int done;
static int num_tests;
static int tests_passed;
// utility function
void TestStart(char *name)
{
num_tests++;
rslt = 1;
printf("-- Testing %s ... ",name);
}
// utility function
void TestEnd()
{
if (rslt) tests_passed++;
printf("%s\n", rslt ? "success" : "fail");
}
// stub function
void exit(int code)
{
if (!done)
{
assert(should_exit==1);
assert(expected_code==code);
longjmp(jump_env, 1);
}
else
{
_exit(code);
}
}
// test case
void test_normal()
{
int jmp_rval;
int r;
TestStart("test_normal");
should_exit = 0;
if (!(jmp_rval=setjmp(jump_env)))
{
r = my_div(12,3);
}
assert(jmp_rval==0);
assert(r==4);
TestEnd();
}
// test case
void test_div0()
{
int jmp_rval;
int r;
TestStart("test_div0");
should_exit = 1;
expected_code = 2;
if (!(jmp_rval=setjmp(jump_env)))
{
r = my_div(2,0);
}
assert(jmp_rval==1);
TestEnd();
}
int main()
{
num_tests = 0;
tests_passed = 0;
done = 0;
test_normal();
test_div0();
printf("Total tests passed: %d\n", tests_passed);
done = 1;
return !(tests_passed == num_tests);
}
この例では、setjmp
テストする関数に入る前に を使用し、スタブ内でexit
呼び出しlongjmp
てテスト ケースに直接戻ります。
また、 redefinedexit
には、実際にプログラムを終了する必要があるかどうかを確認するためにチェックする特別な変数があり、_exit
そうするように呼び出すことにも注意してください。これを行わないと、テスト プログラムが正常に終了しない場合があります。
組み込みについて言及したので、使用しないケースに注意する価値があると思います。コーディング標準で禁止されている場合です。たとえば、MISRA (MISRA-C:2004:Rule 20.7) およびJFS (AV Rule 20) : 「setjmp マクロと longjmp 関数は使用されません。」