[c] C에서 setjmp 및 longjmp의 실제 사용

사람이 정확히 어디 나를 설명 할 수 setjmp()longjmp()기능이 임베디드 프로그래밍에서 실질적으로 사용할 수 있습니까? 나는 이것이 오류 처리 용이라는 것을 알고 있습니다. 하지만 몇 가지 사용 사례를 알고 싶습니다.



답변

오류 처리
다른 많은 함수에 중첩 된 함수에 오류가 있고 오류 처리가 최상위 수준 함수에서만 의미가 있다고 가정합니다.

그 사이에있는 모든 함수가 정상적으로 반환하고 반환 값이나 전역 오류 변수를 평가하여 추가 처리가 의미가 없거나 나쁠 것인지를 결정해야한다면 매우 지루하고 어색 할 것입니다.

이것이 setjmp / longjmp가 의미가있는 상황입니다. 이러한 상황은 다른 언어 (C ++, Java)의 예외가 의미가있는 상황과 유사합니다.

코 루틴
오류 처리 외에도 C에서 setjmp / longjmp가 필요한 또 다른 상황을 생각할 수 있습니다.

코 루틴 을 구현해야하는 경우 입니다.

여기에 약간의 데모 예제가 있습니다. 몇 가지 예제 코드에 대한 Sivaprasad Palas의 요청을 충족하고 TheBlastOne의 질문에 setjmp / longjmp가 코 루틴 구현을 지원하는 방법에 대한 답변을 제공하기를 바랍니다 (비표준 또는 새로운 동작을 기반으로하지 않는 한).

편집 :
그것은 실제로 일 수 있었다 이다 을 할 수있는 정의되지 않은 동작이 longjmp 아래로 (MikeMB의 의견을 참조, 나는 아직 그것을 확인하는 기회가 없었어요하지만) 호출 스택.

#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. 이 주제에 대한질문 .


답변

이론은 오류 처리에 사용할 수 있으므로 체인의 모든 함수에서 오류를 처리 할 필요없이 깊이 중첩 된 호출 체인에서 벗어날 수 있습니다.

모든 영리한 이론과 마찬가지로 이것은 현실을 만날 때 무너집니다. 중간 함수는 메모리를 할당하고, 잠금을 잡고, 파일을 열고, 정리가 필요한 모든 종류의 작업을 수행합니다. 따라서 실제로 setjmp/ longjmp일반적으로 환경 (일부 임베디드 플랫폼)을 완전히 제어 할 수있는 매우 제한된 상황을 제외하고는 나쁜 생각입니다.

내 경험상 대부분의 경우 setjmp/ 사용 longjmp이 작동 한다고 생각할 때마다 프로그램은 명확하고 간단하여 호출 체인의 모든 중간 함수 호출이 오류 처리를 수행 exit할 수 있습니다. 오류가 발생했습니다.


답변

의 조합 setjmplongjmp“최고 강점입니다 goto.” 극도로주의해서 사용하십시오. 그러나 다른 사람들이 설명했듯이 a longjmpget me back to the beginning18 개 계층의 함수에 대해 오류 메시지를 다시 세울 필요없이 신속하게 원할 때 불쾌한 오류 상황에서 벗어나는 데 매우 유용 합니다.

그러나, 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 ++와 같은 완전한 예외 처리”를 수행 할 수 있습니다. 그러나 그것은 매우 지저분하고 잘 작성된 코드에 의존합니다.


답변

임베디드를 언급했기 때문에 사용하지 않는 경우에 주목할 가치가 있다고 생각 합니다. 코딩 표준이이를 금지하는 경우입니다. 예를 들어 MISRA (MISRA-C : 2004 : Rule 20.7) 및 JFS (AV 규칙 20) : “setjmp 매크로 및 longjmp 함수는 사용되지 않습니다.”


답변

setjmplongjmp단위 테스트에서 매우 유용 할 수 있습니다.

다음 모듈을 테스트한다고 가정합니다.

#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할 것인지 확인하는 특수 변수가 있습니다 . 이렇게하지 않으면 테스트 프로그램이 완전히 종료되지 않을 수 있습니다.


답변

, 및 시스템 함수를 사용하여 C에서 Java와 유사한 예외 처리 메커니즘 을 작성했습니다 . 사용자 정의 예외를 포착하지만 . 함수 호출에 걸쳐 작동하는 예외 처리 블록의 무한 중첩 기능을 제공하며 가장 일반적인 두 가지 스레딩 구현을 지원합니다. 링크 타임 상속을 특징으로하는 예외 클래스의 트리 계층 구조를 정의 할 수 있으며, 명령문은이 트리를 탐색하여 캐치 또는 전달이 필요한지 확인합니다.setjmp()longjmp()SIGSEGVcatch

다음은 이것을 사용하여 코드가 어떻게 보이는지에 대한 샘플입니다.

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로 얼마나 멀리 왔는지 놀랐습니다.

관심이 있으시면 외쳐주세요.


답변

손 아래로, setjmp / longjmp의 가장 중요한 사용은 “비 로컬 이동 점프”역할을한다는 것입니다. Goto 명령 (및 for 및 while 루프에서 goto를 사용해야하는 드문 경우)은 동일한 범위에서 가장 안전하게 사용됩니다. goto를 사용하여 범위 (또는 자동 할당)를 건너 뛰면 프로그램 스택이 손상 될 가능성이 높습니다. setjmp / longjmp는 점프하려는 위치에 스택 정보를 저장하여이를 방지합니다. 그런 다음 점프 할 때이 스택 정보를로드합니다. 이 기능이 없으면 C 프로그래머는 setjmp / longjmp 만 해결할 수있는 문제를 해결하기 위해 어셈블리 프로그래밍으로 전환해야 할 가능성이 큽니다. 존재하는 신에게 감사합니다. C 라이브러리의 모든 것은 매우 중요합니다. 필요할 때 알 수 있습니다.