programing

C 코드의 오류 처리

prostudy 2022. 4. 14. 20:37
반응형

C 코드의 오류 처리

C 라이브러리에서 일관된 방식으로 오류를 처리하는 경우 "모범 사례"에 대해 어떻게 생각하십니까?

두 가지 방법을 생각해 보았다.

항상 오류 코드를 반환하십시오.일반적인 기능은 다음과 같다.

MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);

는 항상 오류 포인터 접근 방식을 제공한다.

int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);

첫 번째 접근법을 사용할 때 오류 처리 점검이 기능 호출에 직접 배치되는 곳에 다음과 같은 코드를 작성할 수 있다.

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

여기 오류 처리 코드보다 나은 것 같아.

MYAPIError error;
int size;
size = getObjectSize(h, &error);
if(error != MYAPI_SUCCESS) {
    // Error handling
}

그러나 데이터 반환에 대한 반환 값을 사용하면 코드가 더 읽기 쉽다고 생각한다. 두 번째 예에서는 크기 변수에 무엇인가 쓰여진 것이 분명하다.

내가 왜 그런 접근법 중 하나를 선호해야 하는지, 아니면 그것들을 섞거나 다른 것을 사용해야 하는지에 대해 어떤 생각이 있으십니까?나는 도서관을 여러 가닥으로 묶어서 사용하는 것이 훨씬 더 고통스러운 경향이 있기 때문에 세계적인 오류 상태를 좋아하지 않는다.

편집: 이 문제에 대한 C++ 구체적인 아이디어는 현재 나에게 선택사항이 아니기 때문에 예외를 포함하지 않는 한 흥미로울 것이다.

CMU의 CERT의 슬라이드 세트와 각 공통 C(및 C++) 오류 처리 기법의 사용 시기에 대한 권장사항이 수록되어 있다.가장 좋은 슬라이드 중 하나는 이 결정 트리 입니다.

의사 결정 트리 처리 오류

나는 개인적으로 이 플로우카트에 대해 두 가지를 바꿀 것이다.

첫째로, 나는 때때로 개체들이 오류를 나타내기 위해 반환 값을 사용해야 한다는 것을 분명히 하고 싶다.함수가 객체로부터 데이터를 추출할 뿐 객체를 변이시키지 않는다면 객체 자체의 무결성은 위험하지 않으며 반환 값을 사용하여 오류를 표시하는 것이 더 적절하다.

둘째, C++에서 예외를 사용하는 것이 항상 적절한 것은 아니다.예외는 오류 처리에 할당된 소스 코드의 양을 줄일 수 있고, 대부분 기능 서명에 영향을 주지 않으며, 콜스택을 전달할 수 있는 데이터에 매우 유연하기 때문에 좋다.반면에 다음과 같은 몇 가지 이유로 예외는 올바른 선택이 아닐 수 있다.

  1. C++ 예외에는 매우 특별한 의미론이 있다.만약 당신이 그러한 의미론을 원하지 않는다면, C++ 예외는 나쁜 선택이다.예외는 던져진 직후에 처리해야 하며, 설계는 오류로 인해 콜스택을 몇 단계 풀어야 하는 경우에 유리하다.

  2. 예외를 발생시키는 C++ 기능은 예외를 발생시키지 않기 위해 나중에 포장될 수 없다. 적어도 예외에 대한 전체 비용을 지불하지 않고는 예외를 발생시키지 않는다.에러코드를 반환하는 기능은 C++ 예외를 던지도록 포장할 수 있어 유연성이 뛰어나다.C++의new비변형 변종을 제공함으로써 이 점을 바로 잡는다.

  3. C++ 예외는 상대적으로 비용이 많이 들지만, 이러한 단점은 예외를 합리적으로 사용하는 프로그램들에게 대부분 과장된 것이다.프로그램은 수행이 중요한 코드패스에 예외를 두어서는 안 된다.프로그램이 오류를 보고하고 종료할 수 있는 속도는 중요하지 않다.

  4. C++ 예외를 사용할 수 없는 경우도 있다.말 그대로 C++ 구현에서는 사용할 수 없거나 코드 가이드라인이 금지한다.


원래의 질문은 다중 스레드 문맥에 관한 것이었기 때문에, 현지의 에러 지표기법(Sir Darius대답에 기술된 것)이 원래의 답안에서는 과소평가되었다고 생각한다.그것은 안전하며, 발신자가 오류를 즉시 처리하도록 강요하지 않으며, 오류를 설명하는 임의의 데이터를 묶을 수 있다.단점은 그것이 물체에 의해(혹은 외부적으로 어떻게든 연관되어 있다고 생각함) 보유되어야 하며, 반향 코드보다 무시할 수 있다는 것이다.

나는 그 오류를 가치 반환 방법으로 좋아한다.api를 설계할 때 라이브러리를 최대한 쉽게 사용하려면 다음과 같은 추가 사항을 고려하십시오.

  • 가능한 모든 오류 정보를 하나의 타이핑된 열거형에 저장하고 lib에 사용하십시오.단순히 입력 또는 더 나쁜 결과를 가져오지 말고, 입력 또는 다른 열거값을 반환 코드로 혼합하십시오.

  • 오류를 인간이 읽을 수 있는 것으로 변환하는 기능을 제공한다.간단할 수 있다.그냥 에러-엔진, 콘스탄트 char* out.

  • 나는 이 아이디어가 멀티스레드 사용을 조금 어렵게 만든다는 것을 알지만, 애플리케이션 프로그래머가 글로벌 오류 콜백을 설정할 수 있다면 좋을 것 같다.그렇게 하면 그들은 버그 헌트 세션 동안 콜백에 돌파구를 마련할 수 있을 것이다.

도움이 되길 바래.

난 두 가지 방법을 다 써봤는데 둘 다 나한테 잘 먹혔어.어느 것을 사용하든 나는 항상 이 원칙을 적용하려고 노력한다.

가능한 유일한 오류가 프로그래머 오류일 경우, 오류 코드를 반환하지 말고, 함수 내부의 주장을 사용하십시오.

입력의 유효성을 확인하는 주장은 함수가 기대하는 바를 명확하게 전달하지만, 너무 많은 오류 점검은 프로그램 논리를 모호하게 할 수 있다.모든 다양한 오류 사례에 대해 무엇을 해야 할지 결정하는 것은 설계를 정말로 복잡하게 만들 수 있다.프로그래머가 null 포인터를 통과하지 못하도록 대신 주장할 수 있다면 함수X가 null 포인터를 어떻게 처리해야 하는지 왜 알아내는가?

여기 닐스 피펜브린크의 대답의 처음 2개의 총알을 보여줄 수 있는 간단한 프로그램이 있다.

그의 첫 2발의 총알은 다음과 같다.

  • 가능한 모든 오류 정보를 하나의 타이핑된 열거형에 저장하고 lib에 사용하십시오.단순히 입력 또는 더 나쁜 결과를 가져오지 말고, 입력 또는 다른 열거값을 반환 코드로 혼합하십시오.

  • 오류를 인간이 읽을 수 있는 것으로 변환하는 기능을 제공한다.간단할 수 있다.그냥 에러-엔진, 콘스탄트 char* out.

명명된 모듈을 작성했다고 가정해 보십시오.mymodule먼저 mymodule.h에서 열거형 기반 오류 코드를 정의하고 이러한 코드에 해당하는 오류 문자열을 작성하십시오.여기서 나는 C 문자열의 배열을 사용하고 있다.char *() 이는 첫 번째 열거형 오류 코드의 값이 0일 경우에만 잘 작동하며, 이후 숫자를 조작하지 않는다.만일 당신이 갭이나 다른 시작 값을 가진 오류 코드 번호를 사용한다면, 당신은 단지 매핑된 C 문자열 어레이(아래와 같이)를 사용하는 것에서 스위치 문을 사용하는 함수를 사용하는 것으로 변경하거나 만약/그렇지 않으면 열거된 오류 코드에서 인쇄 가능한 C 문자열로 매핑할 문을 사용하는 것으로 변경해야 할 것이다.선택은 여러분의 것이다.

mymodule.h

/// @brief Error codes for library "mymodule"
typedef enum mymodule_error_e
{
    /// No error
    MYMODULE_ERROR_OK = 0,
    
    /// Invalid arguments (ex: NULL pointer where a valid pointer is required)
    MYMODULE_ERROR_INVARG,

    /// Out of memory (RAM)
    MYMODULE_ERROR_NOMEM,

    /// Make up your error codes as you see fit
    MYMODULE_ERROR_MYERROR, 

    // etc etc
    
    /// Total # of errors in this list (NOT AN ACTUAL ERROR CODE);
    /// NOTE: that for this to work, it assumes your first error code is value 0 and you let it naturally 
    /// increment from there, as is done above, without explicitly altering any error values above
    MYMODULE_ERROR_COUNT,
} mymodule_error_t;

// Array of strings to map enum error types to printable strings
// - see important NOTE above!
const char* const MYMODULE_ERROR_STRS[] = 
{
    "MYMODULE_ERROR_OK",
    "MYMODULE_ERROR_INVARG",
    "MYMODULE_ERROR_NOMEM",
    "MYMODULE_ERROR_MYERROR",
};

// To get a printable error string
const char* mymodule_error_str(mymodule_error_t err);

// Other functions in mymodule
mymodule_error_t mymodule_func1(void);
mymodule_error_t mymodule_func2(void);
mymodule_error_t mymodule_func3(void);

mymodule.c에는 열거형 오류 코드에서 인쇄 가능한 C 문자열로 매핑하는 매핑 기능이 포함되어 있다.

mymodule.c.

#include <stdio.h>

/// @brief      Function to get a printable string from an enum error type
/// @param[in]  err     a valid error code for this module
/// @return     A printable C string corresponding to the error code input above, or NULL if an invalid error code
///             was passed in
const char* mymodule_error_str(mymodule_error_t err)
{
    const char* err_str = NULL;

    // Ensure error codes are within the valid array index range
    if (err >= MYMODULE_ERROR_COUNT)
    {
        goto done;
    }

    err_str = MYMODULE_ERROR_STRS[err];

done:
    return err_str;
}

// Let's just make some empty dummy functions to return some errors; fill these in as appropriate for your 
// library module

mymodule_error_t mymodule_func1(void)
{
    return MYMODULE_ERROR_OK;
}

mymodule_error_t mymodule_func2(void)
{
    return MYMODULE_ERROR_INVARG;
}

mymodule_error_t mymodule_func3(void)
{
    return MYMODULE_ERROR_MYERROR;
}

main.c에는 일부 기능을 호출하고 일부 오류 코드를 인쇄하는 것을 시연하는 테스트 프로그램이 포함되어 있다.

본시

#include <stdio.h>

int main()
{
    printf("Demonstration of enum-based error codes in C (or C++)\n");

    printf("err code from mymodule_func1() = %s\n", mymodule_error_str(mymodule_func1()));
    printf("err code from mymodule_func2() = %s\n", mymodule_error_str(mymodule_func2()));
    printf("err code from mymodule_func3() = %s\n", mymodule_error_str(mymodule_func3()));

    return 0;
}

출력:

C(또는 C++)에서 열거형 기반 오류 코드 시연
= MYMODUL_ERROR_OKmymodule_func1 = MYMODULD_ERROR_OK
= MYMODUL_ERROR_INVARGmymodule_func2 = MYMODULD_ERROR_INVARG의
= MYMODUL_ERROR_MYERRORmodule_func3 = MYMODULD_ERROR_MYERROR.

참조:

여기서 직접 이 코드를 실행하십시오. https://onlinegdb.com/ByEbKLupS.

C에서 오류 처리를 위한 일반적인 접근방식은 오류 코드 반환이다.

그러나 최근에 우리는 나가는 오류 포인터 접근법도 실험했다.

수익률 접근법보다 몇 가지 장점이 있다.

  • 더 의미 있는 목적을 위해 반환 값을 사용할 수 있다.

  • 그 에러 파라미터를 작성해야 하는 것은 에러를 처리하거나 전파하는 것을 상기시킨다.(의 리턴 값을 확인하는 것을 잊지 않는다.fclose,그렇지 않으세요?)

  • 오류 포인터를 사용하면 함수 호출에 따라 전달할 수 있다.만약 어떤 기능이라도 그것을 설정한다면, 그 값은 손실되지 않을 것이다.

  • 에러 변수에 데이터 중단점을 설정하면 에러가 발생한 위치를 먼저 파악할 수 있다.조건부 중단점을 설정하면 특정 오류도 포착할 수 있다.

  • 모든 오류를 처리하는지 여부를 자동으로 검사하는 것이 더 쉬워진다.코드 규칙에 따라 오류 포인터를 다음과 같이 호출할 수 있음err그리고 그것이 마지막 논쟁임에 틀림없어스크립트가 문자열과 일치하도록err);그리고 다음이 있는지 확인한다.if (*err실제로 우리는 매크로라는 것을 만들었다.CER(오류 반환 확인) 및CEG(체크 에러 goto).그래서 우리가 단지 오류로 돌아가고 싶을 때 항상 그것을 타이핑할 필요는 없고, 시각적인 잡동사니를 줄일 수 있다.

그러나 우리 코드의 모든 기능이 이러한 송신 파라미터를 가지고 있는 것은 아니다.이 송신 파라미터는 일반적으로 예외를 두는 경우에 사용된다.

나는 도서관을 만들 때마다 첫 번째 접근법을 사용한다.타이핑된 열거형을 반환 코드로 사용하면 몇 가지 장점이 있다.

  • 함수가 배열 및 길이와 같이 더 복잡한 출력을 반환하는 경우 반환할 임의 구조를 만들 필요가 없다.

    rc = func(..., int **return_array, size_t *array_length);
    
  • 그것은 단순하고 표준화된 오류 처리를 가능하게 한다.

    if ((rc = func(...)) != API_SUCCESS) {
       /* Error Handling */
    }
    
  • 라이브러리 기능에서 간단한 오류 처리가 가능하다.

    /* Check for valid arguments */
    if (NULL == return_array || NULL == array_length)
        return API_INVALID_ARGS;
    
  • 또한 typef'ed 열거형을 사용하면 디버거에 열거형 이름을 볼 수 있다.이를 통해 헤더 파일을 지속적으로 참조할 필요 없이 더 쉽게 디버깅할 수 있다.이 열거형을 문자열로 변환하는 기능이 있는 것도 도움이 된다.

이용되는 접근방식과 무관하게 가장 중요한 문제는 일관성이 있어야 한다는 것이다.이는 기능 및 인수 이름 지정, 인수 순서 지정 및 오류 처리에 적용된다.

나는 이 질의응답을 여러 번 우연히 만났고, 좀 더 포괄적인 답변을 기여하고 싶었다.나는 이것을 생각하는 가장 좋은 방법은 전화를 건 사람에게 오류를 어떻게 돌려줄 것인가, 무엇을 돌려줄 것인가라고 생각한다.

어떻게

함수에서 정보를 반환하는 방법에는 다음 3가지가 있다.

  1. 반환 값
  2. 아웃 인수
  3. Out of Band, 비 로컬 goto(setjmp/longjmp), 파일 또는 전역 범위 변수, 파일 시스템 등을 포함한다.

반환 값

반환할 수 있는 값은 단일 객체일 뿐 임의의 콤플렉스가 될 수 있다.다음은 함수를 반환하는 오류의 예:

  enum error hold_my_beer();

수익률의 한 가지 이점은 덜 침입적인 오류 처리에 대한 호출을 억제할 수 있다는 것이다.

  !hold_my_beer() &&
  !hold_my_cigarette() &&
  !hold_my_pants() ||
  abort();

이것은 가독성에 관한 것일 뿐만 아니라, 그러한 기능 포인터의 배열을 균일한 방법으로 처리할 수도 있다.

아웃 인수

둘 이상의 객체를 통해 인수를 통해 더 많은 것을 반환할 수 있지만, 최선의 방법은 인수의 총 수를 낮게 유지하는 것을 제안한다(예: <=4:

void look_ma(enum error *e, char *what_broke);

enum error e;
look_ma(e);
if(e == FURNITURE) {
  reorder(what_broke);
} else if(e == SELF) {
  tell_doctor(what_broke);
}

아웃오브밴드

setjmp()를 사용하여 플레이스와 int 값을 처리할 방법을 정의하고 longjmp()를 통해 해당 위치로 제어 권한을 전송한다.자세한 내용은 C의 setjmplongjmp의 실제 사용을 참조하십시오.

무엇

  1. 지시자
  2. 코드
  3. 오브젝트
  4. 콜백

지시자

오류 표시기는 문제가 있지만 해당 문제의 본질에 대해서는 아무 것도 없다는 것을 알려준다.

struct foo *f = foo_init();
if(!f) {
  /// handle the absence of foo
}

그러나, 이것은 기능상 오류 상태를 전달하기 위한 가장 덜 강력한 방법이지만, 호출자가 어쨌든 오류에 대해 졸업된 방식으로 응답할 수 없다면 완벽하다.

코드

오류 코드는 발신자에게 문제의 본질에 대해 알려주고, 적절한 응답을 허용할 수 있다(위로부터).반환 값일 수도 있고, 오류 인수 위의 look_ma() 예와 같을 수도 있다.

오브젝트

오류 객체를 통해 발신자에게 임의의 복잡한 문제에 대한 정보를 제공할 수 있다.예를 들어, 오류 코드와 적절한 사람이 읽을 수 있는 메시지.또한 콜렉션을 처리할 때 여러 가지가 잘못되었거나 항목당 오류가 발생했음을 발신자에게 알릴 수 있다.

struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
   if(reason[i] == NOT_FOUND) find(friends[i]);
}

오류 어레이를 미리 할당하는 대신 필요에 따라 동적으로 할당할 수도 있다.

콜백

콜백은 어떤 일이 잘못되었을 때 어떤 행동을 보고 싶은지 기능에게 말할 수 있기 때문에 오류를 처리하는 가장 강력한 방법이다.콜백 인수는 각 함수에 추가할 수 있으며, 또는 다음과 같은 구조물의 인스턴스당 사용자 정의 u가 필요한 경우에만 추가된다.

 struct foo {
    ...
    void (error_handler)(char *);
 };

 void default_error_handler(char *message) { 
    assert(f);
    printf("%s", message);
 }

 void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
    assert(f);
    f->error_handler = eh;
 }

 struct foo *foo_init() {
    struct foo *f = malloc(sizeof(struct foo));
    foo_set_error_handler(f, default_error_handler);
    return f;
 }


 struct foo *f = foo_init();
 foo_something();

콜백의 한 가지 흥미로운 이점은 콜백은 여러 번 호출될 수 있으며, 행복한 경로에 오버헤드가 없는 오류가 없는 경우에는 호출할 수 없다는 것이다.

그러나 통제의 반전이 있다.호출 코드는 콜백이 호출되었는지 여부를 알 수 없다.이와 같이 표시기를 사용하는 것도 이치에 맞을 수 있다.

C:의 기능에 의한 오류 보고에 사용되는 5가지 주요 접근법을 본 적이 있다.

  • 오류 코드 보고 또는 반환 값이 없는 반환 값
  • 오류 코드에만 해당하는 반환 값
  • 유효한 값 또는 오류 코드 값인 반환 값
  • 오류 컨텍스트 정보로 오류 코드를 가져오는 방법을 사용하여 오류를 나타내는 반환 값
  • 오류 컨텍스트 정보가 있는 오류 코드로 값을 반환하는 함수 인수

기능 오류 반환 메커니즘의 선택 외에도 오류 코드 니모닉에 대한 고려와 오류 코드 니모닉이 사용 중인 다른 오류 코드 니모닉과 충돌하지 않도록 보장한다.일반적으로 이것은 3글자 접두사 접근방식을 사용하여 이들을 정의하는 연상기호의 이름을 지정해야 한다.#defineenum또는const static int. "static constance" "#define" 대 "enum"을 참조하십시오.

일단 오류가 감지되면 몇 가지 다른 결과가 있으며, 그것은 함수가 오류 코드와 오류 정보를 제공하는 방법을 고려하는 것일 수 있다.이러한 결과는 실제로 복구 가능한 오류와 복구할 수 없는 오류라는 두 개의 캠프로 나뉜다.

  • 시스템 상태를 문서화한 다음 중단하십시오.
  • 실패한 작업을 기다렸다가 다시 시도하십시오.
  • 인간에게 알리고 원조를 요청하다.
  • 저하된 상태로 계속 실행

오류 유형은 오류의 맥락에 따라 이러한 결과 중 하나 이상을 사용할 수 있다.예를 들어 파일이 없어 열리지 않는 파일은 다른 파일 이름으로 재시도하거나 사용자에게 통지하여 지원을 요청하거나 성능이 저하된 상태에서 실행을 계속할 수 있다.

5가지 주요 접근법에 대한 세부사항

일부 함수는 오류 코드를 제공하지 않는다.기능들은 실패할 수 없거나 실패할 경우, 그들은 조용히 실패한다.이러한 유형의 기능의 예는 여러 가지다.is다음과 같은 문자 테스트 기능isdigit()문자 값이 숫자인지 아닌지를 나타낸다.문자 값은 숫자 또는 알파벳 문자 중 하나이거나 아니다.유사하게strcmp()함수, 두 문자열을 비교하면 어느 문자열이 다른 문자열보다 더 높은지 나타내는 값이 된다.

고장을 나타내는 값이 유효한 결과이기 때문에 오류 코드가 필요하지 않은 경우도 있다.예를 들어strchr()스캔할 문자열에 있는 경우 표준 라이브러리의 함수는 검색된 문자열에 포인터를 반환하거나NULL例句】려면 이 이 경우 캐릭터를 찾지 못하면 유효하고 유용한 지표다.다음을 사용하는 함수strchr()문자열에 없는 것으로 검색된 문자가 성공하도록 요구할 수 있으며 문자 검색은 오류 조건이다.

다른 기능은 오류 코드를 반환하지 않고 대신 외부 메커니즘을 통해 오류를 보고한다.이는 사용자가 설정해야 하는 표준 라이브러리의 대부분의 수학 라이브러리 기능에 의해 사용된다.errno0의 값으로 함수를 호출한 다음 값이 0인지 확인하십시오.errno아 0이다. 할 수 많은 산술 함수의 출력 값 범위에서는 오류를 나타내기 위해 특수 반환 값을 사용할 수 없으며 인터페이스에 오류 보고 인수가 없다.

일부 함수는 작업을 수행하고 성공을 나타내는 가능한 오류 코드 값 중 하나와 오류 코드를 나타내는 나머지 값의 범위를 사용하여 오류 코드 값을 반환한다.예를 들어, 함수는 0의 값을 반환하거나, 반환된 값이 오류 코드인 경우 오류를 나타내는 양 또는 음의 비제로 값을 반환할 수 있다.

일부 함수는 액션을 수행하고 성공하는 경우 유효한 값의 범위에서 값을 반환하거나 오류 코드를 나타내는 잘못된 값 범위에서 값을 반환할 수 있다.간단한 접근법은 유효한 값에 양수 값(0, 1, 2, ...)을 사용하고 오류 코드에 음수 값을 사용하여 다음과 같은 검사를 허용하는 것이다.if(status < 0) return error;.

일부 함수는 유효한 값 또는 잘못된 값을 반환하여 어떤 방법으로 오류 코드를 가져오는 추가 단계가 필요한 오류를 나타낸다.예를 들어fopen()함수는 포인터를 에 반환한다.FILE개체 또는 잘못된 포인터 값을 반환함NULL와 세트errno고장 원인을 나타내는 오류 코드에 연결하십시오. API 은 여러 Windows API 기능이다.HANDLE자원을 참조하는 값은 또한 다음 값을 반환할 수 있다.INVALID_HANDLE_VALUE그리고 기능GetLastError()오류 코드를 얻기 위해 사용된다.표는 OPOS Control Objects의 두 가지 을 제공하도록 요구한다.GetResultCode()그리고GetResultCodeExtended()COM 객체 메서드 호출이 실패할 경우 오류 상태 정보의 검색을 허용하기 위해.

이 같은 접근방식은 오류를 나타내기 위해 사용된 범위를 벗어나는 하나 이상의 값을 가진 유효한 값의 범위가 있는 자원에 대한 핸들 또는 참조를 사용하는 다른 API에서 사용된다.그런 다음 오류 코드와 같은 추가 오류 정보를 가져오는 메커니즘이 제공된다.

부울 값을 반환하는 함수와 유사한 접근법이 사용된다.true기능이 성공적이었음을 나타내기 위해false오류를 나타내기 위해서.그런 다음 프로그래머는 다음과 같은 오류 코드를 결정하기 위해 다른 데이터를 검토해야 한다.GetLastError()Windows API를 사용하여

일부 함수에는 오류 코드 또는 오류 정보를 제공하기 위해 호출된 함수에 대한 메모리 영역의 주소를 포함하는 포인터 인수가 있다.이 접근방식이 실제로 빛을 발하는 부분은 단순한 오류 코드 외에 오류를 지적하는 데 도움이 되는 추가 오류 컨텍스트 정보가 있는 경우다.예를 들어 JSON 문자열 구문 분석 함수는 오류 코드뿐만 아니라 JSON 문자열에서 구문 분석이 실패한 위치로 포인터를 반환할 수 있다.

나는 또한 함수가 오류 정보에 사용되는 인수의 부울 값과 같은 오류 표시기를 반환하는 기능도 본 적이 있다.에 따라 "에에에" 라고 할 수 한다.NULL발신자가 실패의 세부 사항을 알고 싶어하지 않는다는 것을 나타냄

오류 코드나 오류 정보를 반환하는 이러한 접근 방식은 내 경험상 드물게 보이지만, 어떤 이유로든 나는 Windows API에서 종종 또는 XML 파서와 함께 사용되는 것을 본 적이 있다고 생각한다.

다중 스레딩에 대한 고려 사항

다음과 같은 글로벌을 점검할 때와 같은 메커니즘을 통해 추가 오류 코드 액세스 접근 방식을 사용할 때errno또는 다음과 같은 함수를 사용하여GetLastError()여러 스레드에 걸쳐 글로벌을 공유하는 문제가 있다.

현대의 컴파일러와 라이브러리는 스레드 로컬 스토리지를 사용하여 각 스레드가 다른 스레드에 의해 공유되지 않는 고유한 저장소를 갖도록 함으로써 이를 처리한다.그러나 여러 기능이 상태 정보를 위해 동일한 스레드 로컬 저장 위치를 공유하는 문제가 여전히 존재하며, 이는 일부 수용이 필요할 수 있다.예를 들어, 여러 파일을 사용하는 기능은 문제는 모든 예를 들어, 여러의 개의 파일을 사용하는 함수는 모든 파일을 사용하는 문제에 대해 작동해야 할 수 있다를 해결할 필요가 있다.fopen()실패할지도 모르는 전화 한 통을 공유하다.errno같은 실타래로

API가 어떤 종류의 핸들 또는 참조를 사용하는 경우, 오류 코드 저장소를 특정 처리로 만들 수 있다.fopen()함수는 다음을 수행하는 다른 함수로 포장될 수 있다.fopen()그리고 다음 두 가지 기능을 모두 사용하여 API 제어 블록을 설정하십시오.FILE *에 의해 반환된.fopen()의 가치뿐만 아니라errno.

내가 선호하는 접근 방식

내 선호도는 오류 코드가 함수 반환 값으로 반환되어 통화 시 확인하거나 나중에 저장할 수 있도록 하는 것이다.대부분의 경우, 오류는 즉시 처리해야 할 사항이며, 그래서 나는 이 접근법을 선호한다.

내가 기능과 함께 사용한 접근방식은 함수가 단순함을 반환하도록 하는 것이다.struct상태 코드와 반환 값, 두 멤버가 포함된 경우.예를 들면 다음과 같다.

struct FuncRet {
   short  sStatus;  // status or error code
   double dValue;   // calculated value
};

struct FuncRet Func(double dInput)
{
    struct FuncRet = {0, 0};  // sStatus == 0 indicates success

    // calculate return value FuncRet.dValue and set
    // status code FuncRet.sStatus in the event of an error.

    return FuncRet;
}


//  ... source code before using our function.

{
    struct FuncRet s;

    if ((s = Func(aDble)).sStatus == 0) {
        // do things with the valid value s.dValue
    } else {
        // error so deal with the error reported in s.sStatus
    }
}

이것은 내가 오류를 즉시 확인할 수 있게 해준다.반환되는 데이터가 복잡하기 때문에 많은 함수가 실제 값을 반환하지 않고 상태를 반환하게 된다.함수에 의해 하나 이상의 인수가 수정될 수 있지만 함수는 상태 코드 이외의 값을 반환하지 않는다.

UNIX 접근법은 당신의 두 번째 제안과 가장 유사하다.결과 또는 단일 "잘못됨" 값을 반환하십시오.예를 들어, open은 성공 시 파일 설명자를 반환하고 실패 시 -1을 반환한다.고장 시 또한 설정된다.errno어떤 장애가 발생했는지 나타내는 외부 전역 정수.

코코아는 또한 비슷한 접근법을 채택하고 있다.여러 가지 방법으로 BOL을 반환하고NSError **매개 변수, 즉 고장 시 오류를 설정하고 NO를 반환한다.그러면 오류 처리 방식은 다음과 같다.

NSError *error = nil;
if ([myThing doThingError: &error] == NO)
{
  // error handling
}

두 가지 옵션 사이:-)

다른 훌륭한 답변 외에도, 각 통화에 대해 하나의 회선을 저장하기 위해 오류 플래그와 오류 코드를 분리할 것을 제안한다.

if( !doit(a, b, c, &errcode) )
{   (* handle *)
    (* thine  *)
    (* error  *)
}

당신이 많은 오류 검사를 할 때, 이 작은 단순화는 정말로 도움이 된다.

오류를 반환하는 대신 데이터 반환을 금지할 수 있는 방법은 반환 유형에 포장지를 사용하는 것이다.

typedef struct {
    enum {SUCCESS, ERROR} status;
    union {
        int errCode;
        MyType value;
    } ret;
} MyTypeWrapper;

그런 다음 호출된 함수에서 다음을 수행하십시오.

MyTypeWrapper MYAPIFunction(MYAPIHandle h) {
    MyTypeWrapper wrapper;
    // [...]
    // If there is an error somewhere:
    wrapper.status = ERROR;
    wrapper.ret.errCode = MY_ERROR_CODE;

    // Everything went well:
    wrapper.status = SUCCESS;
    wrapper.ret.value = myProcessedData;
    return wrapper;
} 

다음 방법을 사용하면 포장지의 크기가 MyType+1바이트(대부분 컴파일러에서)로 되어 상당히 수익성이 높으며, 함수를 호출할 때 스택에서 다른 인수를 밀어넣을 필요가 없다는 점에 유의하십시오.returnedSize또는returnedError제시된 두 가지 방법 모두).

나는 확실히 첫 번째 해결책이 더 좋다.

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

다음과 같이 약간 수정한다.

int size;
MYAPIError rc;

rc = getObjectSize(h, &size)
if ( rc != MYAPI_SUCCESS) {
  // Error handling
}

덧붙여 나는 결코 합법적인 반환가치를 오류와 혼동하지 않을 것이다. 비록 현재 당신이 그렇게 할 수 있는 기능의 범위가 존재하더라도, 당신은 미래에 어떤 방식으로 기능 구현이 진행될지 결코 알지 못한다.

그리고 만약 우리가 이미 오류 처리에 대해 이야기한다면 나는 제안할 것이다.goto Error;오류 처리 코드로서, 다음이 아닌 경우undo오류 처리를 올바르게 처리하기 위해 함수를 호출할 수 있다.

나도 최근에 이 문제에 대해 곰곰이 생각하고 있었고, 순수하게 국부적인 반환 값을 사용하여 최종적으로 try-catch-finally semantics를 시뮬레이션하는 C용 매크로 몇 개를 작성했다.유용하게 쓰이길 바래.

프로그램을 쓸 때, 초기화 중에는 대개 오류 처리를 위해 스레드를 스핀오프하고, 잠금을 포함한 오류에 대한 특수 구조를 초기화한다.그런 다음 오류를 감지하면 반환 값을 통해 예외의 정보를 구조물에 입력하고 SIGIO를 예외 처리 스레드에 전송한 다음 실행을 계속할 수 없는지 확인하십시오.할 수 없으면 예외 스레드에 SIGURG를 보내 프로그램이 우아하게 중지된다.

여기 내가 흥미롭다고 생각하는 접근법이 있는데, 그것은 약간의 규율을 필요로 한다.

이는 핸들형 변수가 모든 API 기능을 작동하는 인스턴스라고 가정한다.

핸들 뒤의 구조체는 이전의 오류를 필요한 데이터(코드, 메시지...)가 있는 구조체로 저장하며, 사용자는 이 오류 객체에 포인터를 반환하는 기능을 제공받는다.각각의 조작은 사용자가 기능도 호출하지 않고 상태를 확인할 수 있도록 포인트가 있는 객체를 업데이트한다.에러노 패턴과 반대로 에러 코드는 글로벌하지 않아 각 핸들을 적절하게 사용하는 한 접근 스레드를 안전하게 만든다.

예:

MyHandle * h = MyApiCreateHandle();

/* first call checks for pointer nullity, since we cannot retrieve error code
   on a NULL pointer */
if (h == NULL)
     return 0; 

/* from here h is a valid handle */

/* get a pointer to the error struct that will be updated with each call */
MyApiError * err = MyApiGetError(h);

MyApiFileDescriptor * fd = MyApiOpenFile("/path/to/file.ext");

/* we want to know what can go wrong */
if (err->code != MyApi_ERROR_OK) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

MyApiRecord record;

/* here the API could refuse to execute the operation if the previous one
   yielded an error, and eventually close the file descriptor itself if
   the error is not recoverable */
MyApiReadFileRecord(h, &record, sizeof(record));

/* we want to know what can go wrong, here using a macro checking for failure */
if (MyApi_FAILED(err)) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

나는 개인적으로 (오류 표시기를 되돌리는) 이전의 방식을 선호한다.

필요한 경우, 정확한 오류를 알아내기 위해 다른 기능을 사용하는 상태에서, 반환 결과는 오류가 발생했음을 표시해야 한다.

getSize() 예에서는 크기가 항상 0이거나 양수여야 하므로 부정적인 결과를 반환하는 것은 UNIX 시스템 호출과 마찬가지로 오류를 나타낼 수 있다.

오류 객체를 포인터로 전달하여 후자의 접근에 적합한 어떤 라이브러리도 생각할 수 없다.stdio, 등은 모두 리턴 값과 함께 한다.

나는 과거에 C프로그래밍을 많이 했었다.그리고 나는 에러코드 반환값을 정말 인정받지 못했다.그러나 다음과 같은 몇 가지 함정이 있다.

  • 중복된 오류 번호, 글로벌 오류.h 파일로 해결할 수 있다.
  • 오류 코드를 확인하는 것을 잊은 채, 이것은 단서박트와 긴 디버깅 시간으로 해결되어야 한다.그러나 결국 당신은 배우게 될 것이다(또는 당신은 다른 사람이 디버깅을 할 것이라는 것을 알게 될 것이다).

setjmp를 사용한다.

http://en.wikipedia.org/wiki/Setjmp.h

http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02s03.1987

http://www.di.unipi.it/~nids/docs/longjump_try_trow_trow.dll

#include <setjmp.h>
#include <stdio.h>

jmp_buf x;

void f()
{
    longjmp(x,5); // throw 5;
}

int main()
{
    // output of this program is 5.

    int i = 0;

    if ( (i = setjmp(x)) == 0 )// try{
    {
        f();
    } // } --> end of try{
    else // catch(i){
    {
        switch( i )
        {
        case  1:
        case  2:
        default: fprintf( stdout, "error code = %d\n", i); break;
        }
    } // } --> end of catch(i){
    return 0;
}

#include <stdio.h>
#include <setjmp.h>

#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
#define CATCH } else {
#define ETRY } }while(0)
#define THROW longjmp(ex_buf__, 1)

int
main(int argc, char** argv)
{
   TRY
   {
      printf("In Try Statement\n");
      THROW;
      printf("I do not appear\n");
   }
   CATCH
   {
      printf("Got Exception!\n");
   }
   ETRY;

   return 0;
}

첫 번째 접근방식은 IMHO가 더 낫다.

  • 그런 식으로 쓰기 편하다.함수 중간에서 오류가 발견되면 오류 값만 반환하십시오.두 번째 접근 방식에서는 매개변수 중 하나에 오류 값을 할당하고 나서 무언가를 반환해야 하는데.... 그러나 무엇을 반환하시겠습니까? - 올바른 값이 없고 오류 값을 반환하지 않는 경우.
  • 더 대중적이기 때문에 더 쉽게 이해하고 유지할 수 있을 것이다.

지금까지 말한 것 외에도, 오류 코드를 반환하기 전에, 오류가 반환될 때 주장 또는 이와 유사한 진단을 해제하십시오. 이는 추적이 훨씬 쉬워질 것이다.내가 이렇게 하는 방법은 소프트웨어가 진단 모드에 있을 때만 실행되며 로그 파일에 자동으로 보고하거나 화면에서 일시 중지할 수 있는 옵션으로 릴리스 시 컴파일되는 맞춤형 주장을 하는 것이다.

나는 개인적으로 오류 코드를 no_error가 0인 음의 정수로 반환하지만, 그것은 당신에게 가능한 다음의 버그를 남긴다.

if (MyFunc())
 DoSomething();

다른 방법은 고장이 항상 0으로 반환되는 경우, 실제 오류에 대한 세부 정보를 제공하기 위해 LastError() 함수를 사용하는 것이다.

편집:마지막 오류에만 액세스해야 하며 다중 스레드 환경에서 작업하지 않는 경우

true/false(또는 C에서 일하고 bool 변수를 지원하지 않는 경우 #define의 일종)만 반환할 수 있으며 마지막 오류를 저장할 글로벌 오류 버퍼가 있음:

int getObjectSize(MYAPIHandle h, int* returnedSize);
MYAPI_ERROR LastError;
MYAPI_ERROR* getLastError() {return LastError;};
#define FUNC_SUCCESS 1
#define FUNC_FAIL 0

if(getObjectSize(h, &size) != FUNC_SUCCESS ) {
    MYAPI_ERROR* error = getLastError();
    // error handling
}

두 번째 접근방식은 변수의 주소가 함수에 전달될 때 컴파일러가 다른 함수에 대한 후속 호출 동안 레지스터의 값을 유지할 수 없기 때문에 컴파일러가 더 최적화된 코드를 만들 수 있도록 한다.완료 코드는 보통 통화 직후에 한 번만 사용하는 반면, 통화로부터 반환된 "실제" 데이터는 더 자주 사용될 수 있다.

다음 기법을 사용하여 C에서 오류 처리를 선호한다.

struct lnode *insert(char *data, int len, struct lnode *list) {
    struct lnode *p, *q;
    uint8_t good;
    struct {
            uint8_t alloc_node : 1;
            uint8_t alloc_str : 1;
    } cleanup = { 0, 0 };

   // allocate node.
    p = (struct lnode *)malloc(sizeof(struct lnode));
    good = cleanup.alloc_node = (p != NULL);

   // good? then allocate str
    if (good) {
            p->str = (char *)malloc(sizeof(char)*len);
            good = cleanup.alloc_str = (p->str != NULL);
    }

   // good? copy data
    if(good) {
            memcpy ( p->str, data, len );
    }

   // still good? insert in list
    if(good) {
            if(NULL == list) {
                    p->next = NULL;
                    list = p;
            } else {
                    q = list;
                    while(q->next != NULL && good) {
                            // duplicate found--not good
                            good = (strcmp(q->str,p->str) != 0);
                            q = q->next;
                    }
                    if (good) {
                            p->next = q->next;
                            q->next = p;
                    }
            }
    }

   // not-good? cleanup.
    if(!good) {
            if(cleanup.alloc_str)   free(p->str);
            if(cleanup.alloc_node)  free(p);
    }

   // good? return list or else return NULL
    return (good ? list : NULL);
}

출처: http://blog.staila.com/?p=message

참조URL: https://stackoverflow.com/questions/385975/error-handling-in-c-code

반응형