사람이 정확히 어디 나를 설명 할 수 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
할 수 있습니다. 오류가 발생했습니다.
답변
의 조합 setjmp
및 longjmp
“최고 강점입니다 goto
.” 극도로주의해서 사용하십시오. 그러나 다른 사람들이 설명했듯이 a longjmp
는 get me back to the beginning
18 개 계층의 함수에 대해 오류 메시지를 다시 세울 필요없이 신속하게 원할 때 불쾌한 오류 상황에서 벗어나는 데 매우 유용 합니다.
그러나, 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 함수는 사용되지 않습니다.”
답변
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
할 것인지 확인하는 특수 변수가 있습니다 . 이렇게하지 않으면 테스트 프로그램이 완전히 종료되지 않을 수 있습니다.
답변
, 및 시스템 함수를 사용하여 C에서 Java와 유사한 예외 처리 메커니즘 을 작성했습니다 . 사용자 정의 예외를 포착하지만 . 함수 호출에 걸쳐 작동하는 예외 처리 블록의 무한 중첩 기능을 제공하며 가장 일반적인 두 가지 스레딩 구현을 지원합니다. 링크 타임 상속을 특징으로하는 예외 클래스의 트리 계층 구조를 정의 할 수 있으며, 명령문은이 트리를 탐색하여 캐치 또는 전달이 필요한지 확인합니다.setjmp()
longjmp()
SIGSEGV
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로 얼마나 멀리 왔는지 놀랐습니다.
관심이 있으시면 외쳐주세요.
답변
손 아래로, setjmp / longjmp의 가장 중요한 사용은 “비 로컬 이동 점프”역할을한다는 것입니다. Goto 명령 (및 for 및 while 루프에서 goto를 사용해야하는 드문 경우)은 동일한 범위에서 가장 안전하게 사용됩니다. goto를 사용하여 범위 (또는 자동 할당)를 건너 뛰면 프로그램 스택이 손상 될 가능성이 높습니다. setjmp / longjmp는 점프하려는 위치에 스택 정보를 저장하여이를 방지합니다. 그런 다음 점프 할 때이 스택 정보를로드합니다. 이 기능이 없으면 C 프로그래머는 setjmp / longjmp 만 해결할 수있는 문제를 해결하기 위해 어셈블리 프로그래밍으로 전환해야 할 가능성이 큽니다. 존재하는 신에게 감사합니다. C 라이브러리의 모든 것은 매우 중요합니다. 필요할 때 알 수 있습니다.