誰かが正確にsetjmp()
そしてlongjmp()
関数が組み込みプログラミングで実際に使用できる場所を私に説明できますか?これらはエラー処理用であることを知っています。しかし、私はいくつかのユースケースを知りたいのですが。
longjmp()
シグナルハンドラー、特にのようなものから抜け出すために 使用できますBUS ERROR
。この信号は通常、再開できません。組み込みアプリケーションは、安全で堅牢な操作のためにこのケースを処理したいと思うかもしれません。
誰かが正確にsetjmp()
そしてlongjmp()
関数が組み込みプログラミングで実際に使用できる場所を私に説明できますか?これらはエラー処理用であることを知っています。しかし、私はいくつかのユースケースを知りたいのですが。
longjmp()
シグナルハンドラー、特にのようなものから抜け出すために 使用できますBUS ERROR
。この信号は通常、再開できません。組み込みアプリケーションは、安全で堅牢な操作のためにこのケースを処理したいと思うかもしれません。
回答:
エラー処理
他の多くの関数にネストされた関数の深いところにエラーがあり、エラー処理はトップレベルの関数でのみ意味があると仮定します。
中間のすべての関数が正常に戻り、戻り値またはグローバルエラー変数を評価して、それ以上の処理が意味をなさないか悪いことさえあるかを判断する必要がある場合、それは非常に退屈で厄介です。
これは、setjmp / longjmpが意味をなす状況です。これらの状況は、他の言語(C ++、Java)での例外が意味をなす状況に似ています。
コルーチン
エラー処理の他に、Cでsetjmp / longjmpが必要な別の状況も考えられます。
コルーチンを実装する必要がある場合です。
これは小さなデモの例です。いくつかのコード例についてSivaprasad Palasからの要求を満たし、TheBlastOneの質問にsetjmp / longjmpがどのようにしてルーチンの実装をサポートするか(私が見ている限り、非標準または新しい動作に基づいていないことがわかる)の回答に期待します。
編集:
それは実際にコールスタックをダウンすることは未定義の動作であるかもしれません(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を使用する場合は、考慮されないことが多いローカル変数の有効性に影響があることに注意してください。
Cf. このトピックについての私の質問。
routineA
とroutineB
同じスタックを使用し、それだけで非常に原始的なコルーチンのために動作します。がへの最初の呼び出しの後にroutineA
深くネストさroutineC
れ、routineB
これroutineC
がroutineB
コルーチンとして実行されるroutineB
場合、の戻りスタック(ローカル変数だけでなく)も破棄される可能性がありroutineC
ます。したがって、(?alloca()
を呼び出した後にrountineB
)排他スタックを割り当てない場合、この例をレシピとして使用すると深刻な問題が発生します。
理論では、エラー処理にそれらを使用できるため、チェーン内のすべての関数でエラーを処理する必要なく、深くネストされた呼び出しチェーンからジャンプできます。
すべての賢い理論のように、これは現実に出会うときにバラバラになります。中間関数は、メモリを割り当て、ロックを取得し、ファイルを開き、クリーンアップを必要とするあらゆる種類のことを行います。したがって、実際にはsetjmp
/ longjmp
は、環境(一部の組み込みプラットフォーム)を完全に制御できる非常に限られた状況を除いて、通常は悪い考えです。
私の経験では、ほとんどの場合、setjmp
/ を使用するとうまくいくと思うときはいつでもlongjmp
、プログラムは明確でシンプルなので、コールチェーンのすべての中間関数呼び出しでエラー処理を実行できexit
ます。エラーが発生します。
libjpeg
。C ++の場合と同様に、Cルーチンのほとんどのコレクションはstruct *
、集合体として何かを操作するためにa を使用します。中間関数のメモリ割り当てをローカルとして保存する代わりに、構造体に保存できます。これにより、longjmp()
ハンドラはメモリを解放できます。また、これには、すべてのC ++コンパイラーが事実から20年経っても生成するほど多くの非公開例外テーブルがありません。
Like every clever theory this falls apart when meeting reality.
実際、一時的な割り当てなどはlongjmp()
トリッキーになります。これはsetjmp()
、コールスタックで複数回実行する必要があるためです(関数が終了する前に、ある種のクリーンアップを実行する必要がある関数ごとに1回実行し、その後、「例外を再発生させる」必要があります。longjmp()
最初に受け取ったコンテキストに移動することによって)。これらのリソースがの後で変更されたsetjmp()
場合は、がそれらを破壊しvolatile
ないように宣言する必要があるため、さらに悪化longjmp()
します。
組み合わせsetjmp
とlongjmp
「スーパー強みですgoto
」。極度に注意して使用してください。ただし、他の人が説明したように、18層の関数のエラーメッセージをトリクルバックするlongjmp
必要がないので、厄介なエラー状態をget me back to the beginning
すばやく解消したいときに便利です。
ただし、と同様goto
ですが、さらに悪いことに、これをどのように使用するかには十分注意する必要があります。A longjmp
はコードの最初に戻るだけです。これは、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
すべての初期化を保護するために呼び出すことにより、クリーンな例外処理を実装することができます。これは、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
て、テストケースに直接戻ります。
また、再定義exit
には特別な変数があり、プログラムを実際に終了するかどうかを確認し、終了する_exit
ために呼び出すことにも注意してください。これを行わないと、テストプログラムが正常に終了しない場合があります。
Javaのような例外処理メカニズムをCで記述しsetjmp()
、longjmp()
システム関数を使用しました。カスタム例外をキャッチしますが、などのシグナルも送信しSIGSEGV
ます。例外処理ブロックの無限ネストが特徴で、関数呼び出し全体で機能し、最も一般的な2つのスレッド実装をサポートしています。リンク時の継承を特徴とする例外クラスのツリー階層を定義することができ、catch
ステートメントはこのツリーをたどって、キャッチするか渡すかを確認します。
これは、これを使用してコードがどのように見えるかのサンプルです:
try
{
*((int *)0) = 0; /* may not be portable */
}
catch (SegmentationFault, e)
{
long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' };
((void(*)())f)(); /* may not be portable */
}
finally
{
return(1 / strcmp("", ""));
}
そして、これは多くのロジックを含むインクルードファイルの一部です:
#ifndef _EXCEPT_H
#define _EXCEPT_H
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include "Lifo.h"
#include "List.h"
#define SETJMP(env) sigsetjmp(env, 1)
#define LONGJMP(env, val) siglongjmp(env, val)
#define JMP_BUF sigjmp_buf
typedef void (* Handler)(int);
typedef struct _Class *ClassRef; /* exception class reference */
struct _Class
{
int notRethrown; /* always 1 (used by throw()) */
ClassRef parent; /* parent class */
char * name; /* this class name string */
int signalNumber; /* optional signal number */
};
typedef struct _Class Class[1]; /* exception class */
typedef enum _Scope /* exception handling scope */
{
OUTSIDE = -1, /* outside any 'try' */
INTERNAL, /* exception handling internal */
TRY, /* in 'try' (across routine calls) */
CATCH, /* in 'catch' (idem.) */
FINALLY /* in 'finally' (idem.) */
} Scope;
typedef enum _State /* exception handling state */
{
EMPTY, /* no exception occurred */
PENDING, /* exception occurred but not caught */
CAUGHT /* occurred exception caught */
} State;
typedef struct _Except /* exception handle */
{
int notRethrown; /* always 0 (used by throw()) */
State state; /* current state of this handle */
JMP_BUF throwBuf; /* start-'catching' destination */
JMP_BUF finalBuf; /* perform-'finally' destination */
ClassRef class; /* occurred exception class */
void * pData; /* exception associated (user) data */
char * file; /* exception file name */
int line; /* exception line number */
int ready; /* macro code control flow flag */
Scope scope; /* exception handling scope */
int first; /* flag if first try in function */
List * checkList; /* list used by 'catch' checking */
char* tryFile; /* source file name of 'try' */
int tryLine; /* source line number of 'try' */
ClassRef (*getClass)(void); /* method returning class reference */
char * (*getMessage)(void); /* method getting description */
void * (*getData)(void); /* method getting application data */
void (*printTryTrace)(FILE*);/* method printing nested trace */
} Except;
typedef struct _Context /* exception context per thread */
{
Except * pEx; /* current exception handle */
Lifo * exStack; /* exception handle stack */
char message[1024]; /* used by ExceptGetMessage() */
Handler sigAbrtHandler; /* default SIGABRT handler */
Handler sigFpeHandler; /* default SIGFPE handler */
Handler sigIllHandler; /* default SIGILL handler */
Handler sigSegvHandler; /* default SIGSEGV handler */
Handler sigBusHandler; /* default SIGBUS handler */
} Context;
extern Context * pC;
extern Class Throwable;
#define except_class_declare(child, parent) extern Class child
#define except_class_define(child, parent) Class child = { 1, parent, #child }
except_class_declare(Exception, Throwable);
except_class_declare(OutOfMemoryError, Exception);
except_class_declare(FailedAssertion, Exception);
except_class_declare(RuntimeException, Exception);
except_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */
except_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */
except_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */
except_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */
except_class_declare(BusError, RuntimeException); /* SIGBUS */
#ifdef DEBUG
#define CHECKED \
static int checked
#define CHECK_BEGIN(pC, pChecked, file, line) \
ExceptCheckBegin(pC, pChecked, file, line)
#define CHECK(pC, pChecked, class, file, line) \
ExceptCheck(pC, pChecked, class, file, line)
#define CHECK_END \
!checked
#else /* DEBUG */
#define CHECKED
#define CHECK_BEGIN(pC, pChecked, file, line) 1
#define CHECK(pC, pChecked, class, file, line) 1
#define CHECK_END 0
#endif /* DEBUG */
#define except_thread_cleanup(id) ExceptThreadCleanup(id)
#define try \
ExceptTry(pC, __FILE__, __LINE__); \
while (1) \
{ \
Context * pTmpC = ExceptGetContext(pC); \
Context * pC = pTmpC; \
CHECKED; \
\
if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \
pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0) \
{ \
pC->pEx->scope = TRY; \
do \
{
#define catch(class, e) \
} \
while (0); \
} \
else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \
pC->pEx->ready && ExceptCatch(pC, class)) \
{ \
Except *e = LifoPeek(pC->exStack, 1); \
pC->pEx->scope = CATCH; \
do \
{
#define finally \
} \
while (0); \
} \
if (CHECK_END) \
continue; \
if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0) \
pC->pEx->ready = 1; \
else \
break; \
} \
ExceptGetContext(pC)->pEx->scope = FINALLY; \
while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \
while (ExceptGetContext(pC)->pEx->ready-- > 0)
#define throw(pExceptOrClass, pData) \
ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__)
#define return(x) \
{ \
if (ExceptGetScope(pC) != OUTSIDE) \
{ \
void * pData = malloc(sizeof(JMP_BUF)); \
ExceptGetContext(pC)->pEx->pData = pData; \
if (SETJMP(*(JMP_BUF *)pData) == 0) \
ExceptReturn(pC); \
else \
free(pData); \
} \
return x; \
}
#define pending \
(ExceptGetContext(pC)->pEx->state == PENDING)
extern Scope ExceptGetScope(Context *pC);
extern Context *ExceptGetContext(Context *pC);
extern void ExceptThreadCleanup(int threadId);
extern void ExceptTry(Context *pC, char *file, int line);
extern void ExceptThrow(Context *pC, void * pExceptOrClass,
void *pData, char *file, int line);
extern int ExceptCatch(Context *pC, ClassRef class);
extern int ExceptFinally(Context *pC);
extern void ExceptReturn(Context *pC);
extern int ExceptCheckBegin(Context *pC, int *pChecked,
char *file, int line);
extern int ExceptCheck(Context *pC, int *pChecked, ClassRef class,
char *file, int line);
#endif /* _EXCEPT_H */
信号処理といくつかの簿記のためのロジックを含むCモジュールもあります。
私があなたに言うことができる実装することは非常にトリッキーでした、そして私はほとんど終了します。私はそれをできる限りJavaに近づけることを本当に強く求めました。私はCだけでどれほど遠くに行ったのか、意外でした。
興味があれば一言お願いします。
main()
です。この回答に賛成投票してください:-)
確かに、setjmp / longjmpの最も重要な使用法は、「非ローカルgotoジャンプ」として機能することです。gotoコマンド(およびまれにgoto over forおよびwhileループを使用する必要がある場合があります)は、同じスコープで最も安全に使用されます。gotoを使用してスコープ全体(または自動割り当て全体)にジャンプすると、プログラムのスタックが破損する可能性が高くなります。setjmp / longjmpは、ジャンプしたい場所にスタック情報を保存することでこれを回避します。次に、ジャンプすると、このスタック情報がロードされます。この機能がないと、Cプログラマーはsetjmp / longjmpだけが解決できる問題を解決するために、アセンブリプログラミングに頼らざるを得ないでしょう。それが存在する神に感謝します。Cライブラリのすべてが非常に重要です。あなたはそれがいつ必要かを知るでしょう。