나는 오늘 다른 언어에 존재하는 try / catch 블록에 대해 생각하고있었습니다. 잠시 동안 검색했지만 결과가 없습니다. 내가 아는 바에 따르면 C에서 try / catch와 같은 것은 없습니다. 그러나 그것들을 “시뮬레이션”하는 방법이 있습니까?
물론, assert와 다른 트릭이 있지만 try / catch와 같은 것은 없습니다. 감사합니다
답변
C 자체는 예외를 지원하지 않지만 setjmp
및 longjmp
호출 을 사용하여 어느 정도 시뮬레이션 할 수 있습니다 .
static jmp_buf s_jumpBuffer;
void Example() {
if (setjmp(s_jumpBuffer)) {
// The longjmp was executed and returned control here
printf("Exception happened here\n");
} else {
// Normal code execution starts here
Test();
}
}
void Test() {
// Rough equivalent of `throw`
longjmp(s_jumpBuffer, 42);
}
이 웹 사이트에는 setjmp
및 예외를 시뮬레이션하는 방법에 대한 멋진 자습서가 있습니다.longjmp
답변
유사한 오류 처리 상황을 위해 C에서 goto 를 사용 합니다.
이것은 C에서 얻을 수있는 예외와 가장 비슷합니다.
답변
좋아, 나는 이것에 대답하는 것을 거부 할 수 없었다. 먼저 C에 대한 외래 개념이므로 C로 시뮬레이션하는 것이 좋지 않다고 생각합니다.
제한된 버전의 C ++ try / throw / catch를 사용하기 위해 전처리 기와 로컬 스택 변수를 남용 할 수 있습니다 .
버전 1 (로컬 범위 발생)
#include <stdbool.h>
#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
버전 1은 로컬 throw 전용입니다 (함수 범위를 벗어날 수 없음). 코드에서 변수를 선언하는 C99의 기능에 의존합니다 (함수에서 시도가 첫 번째 인 경우 C89에서 작동해야 함).
이 함수는 단지 로컬 var를 만들어 오류가 있는지 알고 goto를 사용하여 catch 블록으로 이동합니다.
예를 들면 :
#include <stdio.h>
#include <stdbool.h>
#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
int main(void)
{
try
{
printf("One\n");
throw();
printf("Two\n");
}
catch(...)
{
printf("Error\n");
}
return 0;
}
이것은 다음과 같이 작동합니다.
int main(void)
{
bool HadError=false;
{
printf("One\n");
HadError=true;
goto ExitJmp;
printf("Two\n");
}
ExitJmp:
if(HadError)
{
printf("Error\n");
}
return 0;
}
버전 2 (범위 점프)
#include <stdbool.h>
#include <setjmp.h>
jmp_buf *g__ActiveBuf;
#define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else
#define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown)
#define throw(x) longjmp(*g__ActiveBuf,1);
버전 2는 훨씬 더 복잡하지만 기본적으로 동일한 방식으로 작동합니다. 현재 함수에서 try 블록으로의 긴 점프를 사용합니다. 그런 다음 try 블록은 if / else를 사용하여 코드 블록을 catch 블록으로 건너 뛰고 로컬 변수가 catch해야하는지 확인합니다.
예제가 다시 확장되었습니다.
jmp_buf *g_ActiveBuf;
int main(void)
{
jmp_buf LocalJmpBuff;
jmp_buf *OldActiveBuf=g_ActiveBuf;
bool WasThrown=false;
g_ActiveBuf=&LocalJmpBuff;
if(setjmp(LocalJmpBuff))
{
WasThrown=true;
}
else
{
printf("One\n");
longjmp(*g_ActiveBuf,1);
printf("Two\n");
}
g_ActiveBuf=OldActiveBuf;
if(WasThrown)
{
printf("Error\n");
}
return 0;
}
이것은 전역 포인터를 사용하므로 longjmp ()는 마지막 실행 시도를 알고 있습니다. 우리는 사용하여 자식 기능도 try / catch 블록을 가질 수 있도록 스택을 남용.
이 코드를 사용하면 여러 가지 단점이 있습니다 (하지만 재미있는 정신 운동입니다).
- 호출되는 해체자가 없기 때문에 할당 된 메모리를 해제하지 않습니다.
- 한 스코프에 둘 이상의 try / catch를 가질 수 없습니다 (중첩 없음).
- 실제로 C ++에서와 같은 예외 또는 기타 데이터를 던질 수 없습니다.
- 스레드로부터 안전하지 않음
- 다른 프로그래머가 해킹을 인식하지 못하고 C ++ try / catch 블록처럼 사용하려고 시도 할 가능성이 높기 때문에 다른 프로그래머를 실패로 설정하고 있습니다.
답변
C99에서는 로컬이 아닌 제어 흐름에 setjmp
/ longjmp
를 사용할 수 있습니다 .
단일 범위 내에서 다중 자원 할당 및 다중 종료가있는 경우 C에 대한 일반 구조화 된 코딩 패턴 은이 예제goto
와 같이 를 사용합니다 . 이것은 C ++가 내부에서 자동 객체의 소멸자 호출을 구현하는 방법과 유사하며, 이것을 열심히 고수한다면 복잡한 함수에서도 어느 정도의 정리를 허용해야합니다.
답변
다른 답변의 일부를 사용하여 간단한 경우를 포함하고 있지만 setjmp
및 longjmp
실제 응용 프로그램에서이 정말 문제 두 가지 문제가 있습니다.
- try / catch 블록의 중첩. 단일 전역 변수를 사용하면
jmp_buf
작동하지 않습니다. - 스레딩. 단일 전역 변수
jmp_buf
는이 상황에서 모든 종류의 고통을 유발합니다.
이것에 대한 해결책 jmp_buf
은 당신이 갈 때 업데이트 되는 스레드-로컬 스택을 유지하는 것입니다. (나는 이것이 lua가 내부적으로 사용하는 것이라고 생각합니다).
그래서 대신에 (JaredPar의 멋진 답변에서)
static jmp_buf s_jumpBuffer;
void Example() {
if (setjmp(s_jumpBuffer)) {
// The longjmp was executed and returned control here
printf("Exception happened\n");
} else {
// Normal code execution starts here
Test();
}
}
void Test() {
// Rough equivalent of `throw`
longjump(s_jumpBuffer, 42);
}
다음과 같은 것을 사용합니다.
#define MAX_EXCEPTION_DEPTH 10;
struct exception_state {
jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
int current_depth;
};
int try_point(struct exception_state * state) {
if(current_depth==MAX_EXCEPTION_DEPTH) {
abort();
}
int ok = setjmp(state->jumpBuffer[state->current_depth]);
if(ok) {
state->current_depth++;
} else {
//We've had an exception update the stack.
state->current_depth--;
}
return ok;
}
void throw_exception(struct exception_state * state) {
longjump(state->current_depth-1,1);
}
void catch_point(struct exception_state * state) {
state->current_depth--;
}
void end_try_point(struct exception_state * state) {
state->current_depth--;
}
__thread struct exception_state g_exception_state;
void Example() {
if (try_point(&g_exception_state)) {
catch_point(&g_exception_state);
printf("Exception happened\n");
} else {
// Normal code execution starts here
Test();
end_try_point(&g_exception_state);
}
}
void Test() {
// Rough equivalent of `throw`
throw_exception(g_exception_state);
}
다시 한 번 더 현실적인 버전은 오류 정보를에 저장하는 방법 exception_state
, 더 나은 처리 방법을 포함 MAX_EXCEPTION_DEPTH
합니다 (버퍼를 늘리기 위해 realloc을 사용하는 등).
면책 조항 : 위의 코드는 테스트없이 작성되었습니다. 순전히 사물을 구성하는 방법에 대한 아이디어를 얻습니다. 다른 시스템과 다른 컴파일러는 스레드 로컬 저장소를 다르게 구현해야합니다. 코드에는 컴파일 오류와 논리 오류가 모두 포함되어있을 수 있으므로 원하는대로 자유롭게 사용할 수 있지만 사용하기 전에 테스트하십시오.)