C의 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에서는 이 컨셉을 시뮬레이트하는 것은 좋지 않다고 생각합니다.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은 로컬 슬로우 전용입니다(함수의 범위를 벗어날 수 없습니다).코드 내의 변수를 선언하는 C99의 능력에 의존합니다(함수에서 시행이 가장 먼저 필요한 경우 C89에서 작동합니다).
이 함수는 로컬 변수를 생성하여 오류가 있었는지 확인하고 goto를 사용하여 캐치 블록으로 이동합니다.
예를 들어 다음과 같습니다.
#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는 훨씬 복잡하지만 기본적으로 동일한 방식으로 작동합니다.현재 기능에서 멀리뛰기를 사용하여 테스트 블록으로 이동합니다.그런 다음 if/else를 사용하여 코드 블록을 캐치 블록으로 건너뛰고 로컬 변수를 체크하여 캐치 여부를 확인합니다.
이 예는 다시 확장되었습니다.
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()는 마지막으로 실행된 시도를 인식합니다.우리는 그래요.
사용하고 있다 스택을 남용하여 하위 기능에도 시행/해제 블록이 있을 수 있습니다.
이 코드를 사용하면 여러 가지 단점이 있습니다(단, 재미있는 정신 운동입니다).
- 호출되는 디컨스트럭터가 없기 때문에 할당된 메모리를 해방할 수 없습니다.
- 범위 내에서 시도/캐치를 두 번 이상 할 수 없습니다(네스트 없음).
- C++와 같은 예외나 기타 데이터는 실제로 던질 수 없습니다.
- 스레드 세이프가 전혀 없음
- 다른 프로그래머가 해킹을 알아차리지 못하고 C++ try/catch block처럼 사용하려고 하기 때문에 다른 프로그래머가 실패하도록 설정하고 있습니다.
이것은 C에서 에러 처리를 하는 또 다른 방법으로 setjmp/longjmp를 사용하는 것보다 퍼포먼스가 우수합니다.유감스럽게도 MSVC에서는 동작하지 않습니다만, GCC/Clang 만을 사용할 수 있는 경우는, 검토해 주세요.구체적으로는 라벨의 주소를 가져와 값에 저장하고 무조건으로 점프할 수 있는 "값으로 라벨" 확장을 사용합니다.예를 들어 설명하겠습니다.
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;
}
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 '%s' failed! %d, %s\n", __FILE__, __LINE__, #X, 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++ 예외 + 파괴자만큼 우아하지는 않습니다.예를 들어, 이러한 방법으로 여러 오류 처리 스택을 하나의 함수 내에 중첩하는 것은 그다지 깨끗하지 않습니다.대신, 이러한 기능을 마찬가지로 오류를 처리하는 자체 포함 하위 함수로 세분화하고, 초기화 + 이렇게 명시적으로 마무리하는 것이 좋습니다.
또, 이것은 1개의 함수내에서만 기능해, 상위 레벨의 발신자가 같은 명시적인 에러 처리 로직을 실장하지 않는 한, 스택의 점프를 계속하는 일은 없습니다.한편, C++ 예외는, 적절한 핸들러를 찾을 때까지 스택의 점프를 계속합니다.또, 임의의 타입을 송신할 수도 없고, 에러 코드만을 송신할 수도 있습니다.
이와 같이 체계적으로 코딩하면(즉, 단일 진입점과 단일 출구점을 사용하여) 어떤 경우에도 실행되는 사전 및 사후("최종") 논리를 쉽게 삽입할 수 있습니다.END 라벨 뒤에 '최종' 논리를 붙이면 됩니다.
유사한 오류 처리 상황에서 C에서 goto를 사용합니다.
이는 C에서 얻을 수 있는 예외 중 가장 가까운 값입니다.
은 간단한 요.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
(아마도 버퍼를 확장하기 위해 재할당을 사용하는 등)
면책사항: 상기 코드는 아무런 테스트 없이 작성되었습니다.그것은 순전히 어떻게 사물을 구조화해야 하는지에 대한 아이디어를 얻기 위한 것이다.시스템이나 컴파일러에 따라 스레드 로컬 스토리지를 다르게 구현해야 합니다.코드에는 컴파일 오류와 로직 오류가 모두 포함되어 있을 수 있습니다.따라서 자유롭게 사용할 수 있는 동안 사용하기 전에 테스트해 보십시오.
빠른 구글 검색은 다른 사람들이 언급한 것처럼 setjmp/longjmp를 사용하는 이와 같은 엉터리 솔루션을 생성합니다.C++/Java의 시도/캐치만큼 간단하고 우아한 것은 없습니다.나는 에이다의 예외적인 처리는 오히려 내가 좋아하는 편이다.
if 스테이트먼트를 사용하여 모든 것을 확인합니다.
Redis는 goto를 사용하여 트라이/캐치를 시뮬레이션하고 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;
}
경고: 다음은 그다지 좋지 않지만 효과가 있습니다.
#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를 합니다.
(유감스럽게도) 메이저 언어는 아니지만 APL에서는 EA 운영(Execute Alternate의 약자)이 있습니다.
사용방법: 'Y' ea 'EA' 'X' (X와 Y는 문자열 또는 함수명으로 제공되는 코드 스니펫입니다.
X 에러가 발생했을 경우는, 대신에 Y(통상은 에러 처리)가 실행됩니다.
C99에서는 로컬 이외의 제어 흐름에 /를 longjmp
사용할 수 있습니다.
단일 범위 내에서 여러 리소스 할당 및 여러 종료가 존재하는 경우 C의 일반적인 구조화된 코딩 패턴은 다음과 같이 사용됩니다.goto
(이 예시와 같이).이는 C++가 후드 아래에 자동 오브젝트의 파괴자 호출을 구현하는 방식과 비슷하며, 이를 꾸준히 유지하면 복잡한 기능에서도 어느 정도 깨끗해질 수 있습니다.
이 작업은 다음과 같이 수행할 수 있습니다.setjmp/longjmp
C. P99에는 C11의 새로운 스레드모델과 일치하는 편리한 툴셋이 준비되어 있습니다.
Win32에서 C를 사용하는 경우 Structured Exception Handling(SEH)을 활용하여 시도/캐치를 시뮬레이션할 수 있습니다.
지원되지 않는 플랫폼에서 C를 사용하는 경우setjmp()
그리고.longjmp()
pjsip 라이브러리의 이 예외처리를 보시면 자체 구현이 가능합니다.
위의 답변을 검토한 후 자동으로 중첩된 예외를 잘 처리하는 시스템을 구축했습니다.시스템을 테스트하기 위해 작성한 코드는 다음과 같습니다.
#include "MyOtherTricks.h"
#include "Exceptions.h"
void Testing_InnerMethod();
void Testing_PossibleExceptionThrower();
void TestExceptionHandling()
{
try
{
Testing_InnerMethod();
Say("The inner method exited without an exception.");
}
catch (Exception)
{
Say("I caught an Exception that the inner method did not catch.");
}
end_try
}
void Testing_InnerMethod()
{
try
{
Say("I am in a try block.");
Testing_PossibleExceptionThrower();
Say("The possible exception thrower didn't throw an exception.");
}
catch (ExceptionSubtype1)
Say("I caught an exception, subtype 1.");
catch (ExceptionSubtype2)
{
Say("I caught an exception, subtype 2.");
Say("I will now rethrow it.");
throw(exception);
}
end_try
}
void Testing_PossibleExceptionThrower()
{
Say("Here is the possible exception thrower.");
throw(new(ExceptionSubtype2)); // To further test exception handling, replace ExceptionSubtype2 in this line with Exception or ExceptionSubtype1, or comment out this line entirely.
Say("No, I won't throw an exception!");
}
예제 코드는 Exceptions.h와 Exceptions.c의 2개의 파일에 의존합니다.여기 예외가 있습니다.h:
#include <setjmp.h>
extern jmp_buf* Exception_Handler;
#define try do \
{ \
jmp_buf* outerExceptionHandler = Exception_Handler; \
jmp_buf exceptionHandler; \
Exception_Handler = &exceptionHandler; \
Exception exception = (Exception)setjmp(exceptionHandler); \
if (exception != 0) Exception_Handler = outerExceptionHandler; \
if (exception == 0) \
{ \
// The try block goes here. It must not include a return statement or anything else that exits the try...end_try block, because then the outer exception handler will not be restored.
#define catch(exceptionType) Exception_Handler = outerExceptionHandler; \
} \
else if (Object_IsSomeTypeOf(exception, exceptionType)) \
{
// The catch block goes here. It may include a return statement or anything else that exits the try...end_try block. A break statement will exit only the try...end_try block.
#define end_try } \
else \
throw(exception); \
} while(0);
void throw(Exception exception);
다음은 예외사항입니다.c:
#include "MyOtherTricks.h"
#include "Exceptions.h"
jmp_buf* Exception_Handler = 0;
void throw(Exception exception)
{
if (Exception_Handler == 0) FailBecause("Uncaught exception.");
longjmp(*Exception_Handler, (int)exception);
}
이 코드는 여기에 포함되지 않은 몇 가지 추가 메서드를 참조합니다(C의 클래스 상속은 토픽에서 벗어납니다).이 코드를 사용하기 위해서는 몇 가지 사항을 대체할 수 있을 정도로 이 코드를 잘 이해해야 합니다.특히 다른 유형의 예외를 구별하려면 이 코드가 Object_IsSomeTypeOf(new(ExceptionSubtype1, Exception)가 true를 반환하고 Object_IsSomeTypeOf(new(ExceptionSubtype1), ExceptionSubtype2)가 false를 반환하고 있음을 전제로 합니다.TypeOf 매크로 또는 다른 매크로로 바꿉니다.
언급URL : https://stackoverflow.com/questions/10586003/try-catch-statements-in-c
'programing' 카테고리의 다른 글
Vuex 모듈에서 관찰자 유형 대신 개체 값 가져오기 (0) | 2022.08.13 |
---|---|
오류: "[X][mM][lL]"과(와) 일치하는 처리 명령 대상은 허용되지 않습니다. (0) | 2022.08.13 |
Vue.js 3 - Vue 컴포넌트 간에 데이터를 전달하고 두 뷰 모두 업데이트하려면 어떻게 해야 합니까? (0) | 2022.08.13 |
Vuex는 내부 첫 번째 행의 버튼을 클릭할 때만 컴포넌트를 갱신합니다. (0) | 2022.08.13 |
쓰기 전용 포인터 유형 (0) | 2022.08.13 |