나는 오늘 다른 언어에 존재하는 try / catch 블록에 대해 생각하고있었습니다. 잠시 동안 검색했지만 결과가 없습니다. 내가 아는 바에 따르면 C에서 try / catch와 같은 것은 없습니다. 그러나 그것들을 "시뮬레이션"하는 방법이 있습니까?
물론, assert와 다른 트릭이 있지만 try / catch와 같은 것은 없습니다. 감사합니다
나는 오늘 다른 언어에 존재하는 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
try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; 제대로 작동 하지 않는 것과 같은 작업 을 수행 하면?
유사한 오류 처리 상황을 위해 C에서 goto 를 사용 합니다.
이것은 C에서 얻을 수있는 예외와 가장 비슷합니다.
goto오류 처리에 더 많이 사용되지만 어떻게 될까요? 문제는 오류 처리에 관한 것이 아니라 try / catch 등가물에 관한 것입니다. goto동일한 기능으로 제한되기 때문에 try / catch와 동등하지 않습니다.
goto. 검색 goto동등한 "던져"를, 그리고 finish는 "캐치"에 대한 상응.
좋아, 나는 이것에 대답하는 것을 거부 할 수 없었다. 먼저 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 블록을 가질 수 있도록 스택을 남용.
이 코드를 사용하면 여러 가지 단점이 있습니다 (하지만 재미있는 정신 운동입니다).
bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}있습니다. 그러나 지역 변수도 재정의되므로 상황이 조금 어려워집니다.
다른 답변의 일부를 사용하여 간단한 경우를 포함하고 있지만 setjmp및 longjmp실제 응용 프로그램에서이 정말 문제 두 가지 문제가 있습니다.
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을 사용하는 등).
면책 조항 : 위의 코드는 테스트없이 작성되었습니다. 순전히 사물을 구성하는 방법에 대한 아이디어를 얻습니다. 다른 시스템과 다른 컴파일러는 스레드 로컬 저장소를 다르게 구현해야합니다. 코드에는 컴파일 오류와 논리 오류가 모두 포함되어있을 수 있으므로 원하는대로 자유롭게 사용할 수 있지만 사용하기 전에 테스트하십시오.)
이 작업 setjmp/longjmp은 C에서 수행 할 수 있습니다 . P99 에는 C11의 새로운 스레드 모델과 일치하는 매우 편리한 도구 세트가 있습니다.
이것은 setjmp / longjmp를 사용하는 것보다 성능이 더 뛰어난 C에서 오류 처리를 수행하는 또 다른 방법입니다. 불행히도 MSVC에서는 작동하지 않지만 GCC / Clang 만 사용하는 것이 옵션 인 경우 고려할 수 있습니다. 특히 "label as value"확장을 사용하여 레이블 주소를 가져와 값에 저장하고 무조건 점프 할 수 있습니다. 예를 들어 설명하겠습니다.
GameEngine *CreateGameEngine(GameEngineParams const *params)
{
/* Declare an error handler variable. This will hold the address
to jump to if an error occurs to cleanup pending resources.
Initialize it to the err label which simply returns an
error value (NULL in this example). The && operator resolves to
the address of the label err */
void *eh = &&err;
/* Try the allocation */
GameEngine *engine = malloc(sizeof *engine);
if (!engine)
goto *eh; /* this is essentially your "throw" */
/* Now make sure that if we throw from this point on, the memory
gets deallocated. As a convention you could name the label "undo_"
followed by the operation to rollback. */
eh = &&undo_malloc;
/* Now carry on with the initialization. */
engine->window = OpenWindow(...);
if (!engine->window)
goto *eh; /* The neat trick about using approach is that you don't
need to remember what "undo" label to go to in code.
Simply go to *eh. */
eh = &&undo_window_open;
/* etc */
/* Everything went well, just return the device. */
return device;
/* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
원하는 경우 정의에서 공통 코드를 리팩터링하여 자신의 오류 처리 시스템을 효과적으로 구현할 수 있습니다.
/* Put at the beginning of a function that may fail. */
#define declthrows void *_eh = &&err
/* Cleans up resources and returns error result. */
#define throw goto *_eh
/* Sets a new undo checkpoint. */
#define undo(label) _eh = &&undo_##label
/* Throws if [condition] evaluates to false. */
#define check(condition) if (!(condition)) throw
/* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */
#define checkpoint(label, condition) { check(condition); undo(label); }
그런 다음 예는
GameEngine *CreateGameEngine(GameEngineParams const *params)
{
declthrows;
/* Try the allocation */
GameEngine *engine = malloc(sizeof *engine);
checkpoint(malloc, engine);
/* Now carry on with the initialization. */
engine->window = OpenWindow(...);
checkpoint(window_open, engine->window);
/* etc */
/* Everything went well, just return the device. */
return device;
/* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
경고 : 다음은별로 좋지 않지만 제대로 작동합니다.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
unsigned int id;
char *name;
char *msg;
} error;
#define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " ‘%s_error’ " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__)
#define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__)
#define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg)
#define _errordef(n, _id) \
error* new_##n##_error_msg(char* msg) { \
error* self = malloc(sizeof(error)); \
self->id = _id; \
self->name = #n; \
self->msg = msg; \
return self; \
} \
error* new_##n##_error() { return new_##n##_error_msg(""); }
#define errordef(n) _errordef(n, __COUNTER__ +1)
#define try(try_block, err, err_name, catch_block) { \
error * err_name = NULL; \
error ** __err = & err_name; \
void __try_fn() try_block \
__try_fn(); \
void __catch_fn() { \
if (err_name == NULL) return; \
unsigned int __##err_name##_id = new_##err##_error()->id; \
if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \
printuncaughterr(); \
else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \
catch_block \
} \
__catch_fn(); \
}
#define throw(e) { *__err = e; return; }
_errordef(any, 0)
용법:
errordef(my_err1)
errordef(my_err2)
try ({
printf("Helloo\n");
throw(new_my_err1_error_msg("hiiiii!"));
printf("This will not be printed!\n");
}, /*catch*/ any, e, {
printf("My lovely error: %s %s\n", e->name, e->msg);
})
printf("\n");
try ({
printf("Helloo\n");
throw(new_my_err2_error_msg("my msg!"));
printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
printerr("%s", e->msg);
})
printf("\n");
try ({
printf("Helloo\n");
throw(new_my_err1_error());
printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
printf("Catch %s if you can!\n", e->name);
})
산출:
Helloo
My lovely error: my_err1 hiiiii!
Helloo
/home/naheel/Desktop/aa.c:28: error: ‘my_err2_error’ my msg!
Helloo
/home/naheel/Desktop/aa.c:38: uncaught error: ‘my_err1_error’
이것은 중첩 된 함수와 __COUNTER__. gcc를 사용하는 경우 안전한 편입니다.
Redis는 goto를 사용하여 try / catch를 시뮬레이션합니다. IMHO는 매우 깨끗하고 우아합니다.
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */
int rdbSave(char *filename) {
char tmpfile[256];
FILE *fp;
rio rdb;
int error = 0;
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
strerror(errno));
return REDIS_ERR;
}
rioInitWithFile(&rdb,fp);
if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
errno = error;
goto werr;
}
/* Make sure data will not remain on the OS's output buffers */
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
if (rename(tmpfile,filename) == -1) {
redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
unlink(tmpfile);
return REDIS_ERR;
}
redisLog(REDIS_NOTICE,"DB saved on disk");
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = REDIS_OK;
return REDIS_OK;
werr:
fclose(fp);
unlink(tmpfile);
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
return REDIS_ERR;
}
errno시스템 호출이 실패한 직후에만 사용해야하며 나중에 세 번 호출하지 않아야합니다.
C에서는 명시적인 오류 처리를 위해 if + goto를 수동으로 사용하여 자동 "객체 재 확보"와 함께 예외를 "시뮬레이션"할 수 있습니다.
나는 종종 다음과 같은 C 코드를 작성합니다 (오류 처리를 강조하기 위해 끓여서).
#include <assert.h>
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
if ( ( ret = foo_init( f ) ) )
goto FAIL;
if ( ( ret = goo_init( g ) ) )
goto FAIL_F;
if ( ( ret = poo_init( p ) ) )
goto FAIL_G;
if ( ( ret = loo_init( l ) ) )
goto FAIL_P;
assert( 0 == ret );
goto END;
/* error handling and return */
/* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
이것은 완전히 표준 ANSI C이고, 메인 라인 코드에서 오류 처리를 분리하고, C ++처럼 초기화 된 객체의 (수동) 스택 해제를 허용하며, 여기서 무슨 일이 일어나고 있는지 완전히 분명합니다. 각 지점에서 명시 적으로 실패를 테스트하기 때문에 오류가 발생할 수있는 각 위치에 특정 로깅 또는 오류 처리를 삽입하기가 더 쉽습니다.
약간의 매크로 마법이 마음에 들지 않으면 스택 추적으로 오류를 로깅하는 것과 같은 다른 작업을 수행하면서 더 간결하게 만들 수 있습니다. 예를 들면 :
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, ret, strerror( ret ) ); goto LABEL; } while ( 0 )
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
TRY( ret = foo_init( f ), FAIL );
TRY( ret = goo_init( g ), FAIL_F );
TRY( ret = poo_init( p ), FAIL_G );
TRY( ret = loo_init( l ), FAIL_P );
assert( 0 == ret );
goto END;
/* error handling and return */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
물론 이것은 C ++ 예외 + 소멸자만큼 우아하지는 않습니다. 예를 들어, 이러한 방식으로 하나의 함수 내에 여러 오류 처리 스택을 중첩하는 것은 그리 깔끔하지 않습니다. 대신, 당신은 아마도 오류를 유사하게 처리하고 초기화 + 마무리하는 자체 포함 하위 함수로 나누고 싶을 것입니다.
이것은 또한 단일 함수 내에서만 작동하며 상위 수준 호출자가 유사한 명시 적 오류 처리 논리를 구현하지 않는 한 스택 위로 계속 올라가지 않는 반면, C ++ 예외는 적절한 핸들러를 찾을 때까지 스택 위로 계속 점프합니다. 또한 임의의 유형을 던지는 것을 허용하지 않고 대신 오류 코드 만 허용합니다.
이러한 방식으로 체계적으로 코딩하면 (즉, 단일 진입 점과 단일 종료점을 사용하여) 무엇이든 실행될 사전 및 사후 ( "최종") 논리를 삽입하는 것이 매우 쉽습니다. END 레이블 뒤에 "최종"로직을 넣습니다.
Win32와 함께 C를 사용하는 경우 구조적 예외 처리 (SEH) 를 활용하여 try / catch를 시뮬레이션 할 수 있습니다.
당신이 지원하지 않는 플랫폼에서 C 사용하는 경우 setjmp()와 longjmp()이것 좀 봐 가지고, 예외 처리 pjsip 라이브러리를, 그것의 자신의 구현을 제공 않습니다
아마도 주요 언어는 아니지만 (불행히도) APL에는 ⎕EA 연산 (대체 실행을 의미)이 있습니다.
사용법 : 'Y'⎕EA 'X'여기서 X와 Y는 문자열 또는 함수 이름으로 제공되는 코드 조각입니다.
X에 오류가 발생하면 Y (일반적으로 오류 처리)가 대신 실행됩니다.