[c] C에서 catch 문 시도

나는 오늘 다른 언어에 존재하는 try / catch 블록에 대해 생각하고있었습니다. 잠시 동안 검색했지만 결과가 없습니다. 내가 아는 바에 따르면 C에서 try / catch와 같은 것은 없습니다. 그러나 그것들을 “시뮬레이션”하는 방법이 있습니까?
물론, assert와 다른 트릭이 있지만 try / catch와 같은 것은 없습니다. 감사합니다



답변

C 자체는 예외를 지원하지 않지만 setjmplongjmp호출 을 사용하여 어느 정도 시뮬레이션 할 수 있습니다 .

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 ++가 내부에서 자동 객체의 소멸자 호출을 구현하는 방법과 유사하며, 이것을 열심히 고수한다면 복잡한 함수에서도 어느 정도의 정리를 허용해야합니다.


답변

다른 답변의 일부를 사용하여 간단한 경우를 포함하고 있지만 setjmplongjmp실제 응용 프로그램에서이 정말 문제 두 가지 문제가 있습니다.

  1. try / catch 블록의 중첩. 단일 전역 변수를 사용하면 jmp_buf작동하지 않습니다.
  2. 스레딩. 단일 전역 변수 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을 사용하는 등).

면책 조항 : 위의 코드는 테스트없이 작성되었습니다. 순전히 사물을 구성하는 방법에 대한 아이디어를 얻습니다. 다른 시스템과 다른 컴파일러는 스레드 로컬 저장소를 다르게 구현해야합니다. 코드에는 컴파일 오류와 논리 오류가 모두 포함되어있을 수 있으므로 원하는대로 자유롭게 사용할 수 있지만 사용하기 전에 테스트하십시오.)


답변

빠른 구글 검색 수익률은 같은 솔루션을 kludgey 다른 언급이 그 사용 setjmp는 / longjmp를. C ++ / Java의 try / catch만큼 간단하고 우아한 것은 없습니다. 나는 Ada의 예외 처리에 다소 부분적입니다.

if 문으로 모든 것을 확인하십시오 🙂


답변

이 작업 setjmp/longjmp은 C에서 수행 할 수 있습니다 . P99 에는 C11의 새로운 스레드 모델과 일치하는 매우 편리한 도구 세트가 있습니다.