Cでのsetjmpおよびlongjmpの実用的な使用法


96

誰かが正確にsetjmp()そしてlongjmp()関数が組み込みプログラミングで実際に使用できる場所を私に説明できますか?これらはエラー処理用であることを知っています。しかし、私はいくつかのユースケースを知りたいのですが。


他のプログラミングと同様のエラー処理用。使い方の違いがわかりませんか???
Tony The Lion


スピードは?はい。それは、a)ループよりも実行速度が遅いため、およびb)簡単に最適化できないためです(遅延の削除など)。だからsetjmp&longjmpは明確に支配する!
TheBlastOne 2013

与えられたものとは別の答えはここにあります。stackoverflow.com/ questions / 7334595 /…longjmp()シグナルハンドラー、特にのようなものから抜け出すために 使用できますBUS ERROR。この信号は通常、再開できません。組み込みアプリケーションは、安全で堅牢な操作のためにこのケースを処理したいと思うかもしれません。
アートレスノイズ2013

そして、の性能差についてsetjmpBSDとLinuxの間で、参照「タイミングのsetjmp、および標準の喜び」を使用して示唆しています、sigsetjmp
Ioannis Filippidis

回答:


81

エラー処理
他の多くの関数にネストされた関数の深いところにエラーがあり、エラー処理はトップレベルの関数でのみ意味があると仮定します。

中間のすべての関数が正常に戻り、戻り値またはグローバルエラー変数を評価して、それ以上の処理が意味をなさないか悪いことさえあるかを判断する必要がある場合、それは非常に退屈で厄介です。

これは、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. このトピックについての私の質問


2
setjmpが準備し、longjmpが現在の呼び出しスコープからジャンプしてsetjmpスコープに戻るので、コルーチンの実装はどのようにサポートされますか?longjmpが出力したルーチンの実行を継続する方法がわかりません。
TheBlastOne 2013

2
@TheBlastOne ウィキペディアの記事を参照してください。あなたがあなたのsetjmp前にいるなら、あなたは実行を続けることができますlongjmp。これは非標準です。
Potatoswatter 2013

9
コルーチンは、例に示されているのと同じスタックではなく、別々のスタックで実行する必要があります。通りroutineAroutineB同じスタックを使用し、それだけで非常に原始的なコルーチンのために動作します。がへの最初の呼び出しの後にroutineA深くネストさroutineCれ、routineBこれroutineCroutineBコルーチンとして実行されるroutineB場合、の戻りスタック(ローカル変数だけでなく)も破棄される可能性がありroutineCます。したがって、(?alloca()を呼び出した後にrountineB)排他スタックを割り当てない場合、この例をレシピとして使用すると深刻な問題が発生します。
Tino

6
回答で、コールスタックを(AからBに)ジャンプするのは未定義の動作であることを述べてください。
MikeMB、2015年

1
また、脚注248)には次のように記載されています。「たとえば、returnステートメントを実行するか、別のlongjmp呼び出しが原因で、ネストされた呼び出しのセットの最初の関数でsetjmp呼び出しに転送されました。」そのため、関数からlongjmp関数を呼び出して、コールスタックのさらに上のポイントまで呼び出すと、その関数も終了するため、後で関数にジャンプして戻ります。
MikeMB 2015年

18

理論では、エラー処理にそれらを使用できるため、チェーン内のすべての関数でエラーを処理する必要なく、深くネストされた呼び出しチェーンからジャンプできます。

すべての賢い理論のように、これは現実に出会うときにバラバラになります。中間関数は、メモリを割り当て、ロックを取得し、ファイルを開き、クリーンアップを必要とするあらゆる種類のことを行います。したがって、実際にはsetjmp/ longjmpは、環境(一部の組み込みプラットフォーム)を完全に制御できる非常に限られた状況を除いて、通常は悪い考えです。

私の経験では、ほとんどの場合、setjmp/ を使用するとうまくいくと思うときはいつでもlongjmp、プログラムは明確でシンプルなので、コールチェーンのすべての中間関数呼び出しでエラー処理を実行できexitます。エラーが発生します。


3
見てくださいlibjpeg。C ++の場合と同様に、Cルーチンのほとんどのコレクションはstruct *、集合体として何かを操作するためにa を使用します。中間関数のメモリ割り当てをローカルとして保存する代わりに、構造体に保存できます。これにより、longjmp()ハンドラはメモリを解放できます。また、これには、すべてのC ++コンパイラーが事実から20年経っても生成するほど多くの非公開例外テーブルがありません。
アートレスノイズ2013

Like every clever theory this falls apart when meeting reality.実際、一時的な割り当てなどはlongjmp()トリッキーになります。これはsetjmp()、コールスタックで複数回実行する必要があるためです(関数が終了する前に、ある種のクリーンアップを実行する必要がある関数ごとに1回実行し、その後、「例外を再発生させる」必要があります。longjmp()最初に受け取ったコンテキストに移動することによって)。これらのリソースがの後で変更されたsetjmp()場合は、がそれらを破壊しvolatileないように宣言する必要があるため、さらに悪化longjmp()します。
sevko 2015

10

組み合わせsetjmplongjmp「スーパー強みですgoto」。極度に注意して使用してください。ただし、他の人が説明したように、18層の関数のエラーメッセージをトリクルバックするlongjmp必要がないので、厄介なエラー状態をget me back to the beginningすばやく解消したいときに便利です。

ただし、と同様gotoですが、さらに悪いことに、これをどのように使用するかには十分注意する必要があります。A longjmpはコードの最初に戻るだけです。これは、setjmpsetjmp開始された状態に戻るまでの間に変更された可能性のある他のすべての状態には影響しません。したがって、割り当て、ロック、半分初期化されたデータ構造などは、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 ++のような完全な例外処理」を行うことができます。しかし、それはかなり面倒で、コードが適切に記述されていることに依存しています。


+1、もちろん、理論上は、setjmpすべての初期化を保護するために呼び出すことにより、クリーンな例外処理を実装することができます。これは、C ++で…スレッド化に使用することは非標準であることを言及する価値があります。
Potatoswatter 2013

8

あなたが埋め込みについて言及しているので、私はそれが非使用ケースに注意する価値があると思います:あなたのコーディング標準がそれを禁止するとき。たとえば、MISRA(MISRA-C:2004:Rule 20.7)およびJFS(AVルール20):「setjmpマクロとlongjmp関数は使用しないでください。」


8

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ために呼び出すことにも注意してください。これを行わないと、テストプログラムが正常に終了しない場合があります。


6

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だけでどれほど遠くに行ったのか、意外でした。

興味があれば一言お願いします。


1
カスタム例外に対する実際のコンパイラサポートがなくても、これが可能であることに驚いています。しかし、本当に興味深いのは、信号がどのように例外に変換されるかです。
Paul Stelian

私は一つ質問します:結局捕まらない例外についてはどうですか?main()はどのように終了しますか?
Paul Stelian

1
@PaulStelianそして、これは、捕獲されなかった例外でどのように終了するかに対するあなたの答えmain()です。この回答に賛成投票してください:-)
意味に関する事項

1
@PaulStelianああ、私はあなたが今何を言っているのか分かります。キャッチされない実行時例外は、一般的な(プラットフォームに依存する)回答が適用されるように再度発生したと思います。キャッチされなかったカスタム例外が出力され、無視されました。1999年4月のコードをGitHubに投稿ProgagationしたREADMEのセクションを参照してください(編集した回答のリンクを参照)。見てください。それは割るのが難しいナットでした。あなたの考えを聞いていただければ嬉しいです。
意味の問題

2
READMEをざっと見て、かなりいいものでした。つまり、基本的にはJavaScriptの非同期関数と同様に、最も外側のtryブロックに伝播して報告されます。いいね。後でソースコード自体を見ていきます。
Paul Stelian

0

確かに、setjmp / longjmpの最も重要な使用法は、「非ローカルgotoジャンプ」として機能することです。gotoコマンド(およびまれにgoto over forおよびwhileループを使用する必要がある場合があります)は、同じスコープで最も安全に使用されます。gotoを使用してスコープ全体(または自動割り当て全体)にジャンプすると、プログラムのスタックが破損する可能性が高くなります。setjmp / longjmpは、ジャンプしたい場所にスタック情報を保存することでこれを回避します。次に、ジャンプすると、このスタック情報がロードされます。この機能がないと、Cプログラマーはsetjmp / longjmpだけが解決できる問題を解決するために、アセンブリプログラミングに頼らざるを得ないでしょう。それが存在する神に感謝します。Cライブラリのすべてが非常に重要です。あなたはそれがいつ必要かを知るでしょう。


「Cライブラリのすべてが非常に重要です。」廃止予定のものや、ロケールのように決して良くないものはたくさんあります。
qwr
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.