[c] 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
}

그러나 데이터를 반환하기 위해 반환 값을 사용하면 코드를 더 쉽게 읽을 수 있다고 생각합니다. 두 번째 예제에서 size 변수에 무언가가 쓰여진 것은 분명합니다.

내가 왜 이러한 접근 방식을 선호하거나 혼합하거나 다른 것을 사용해야하는지에 대한 아이디어가 있습니까? 라이브러리의 멀티 스레드 사용을보다 고통스럽게 만드는 경향이 있기 때문에 전역 오류 상태에 관심이 없습니다.

편집 : 이것에 대한 C ++ 관련 아이디어는 예외가 아니기 때문에 현재로서는 나에게 옵션이 아니기 때문에 들리는 것이 흥미로울 것입니다 …



답변

오류를 반환 값 방식으로 좋아합니다. API를 디자인하고 라이브러리를 가능한 한 고통없이 사용하려면 다음 추가 사항에 대해 생각하십시오.

  • 가능한 모든 오류 상태를 하나의 typedef 열거 형 열거 형에 저장하고 lib에서 사용하십시오. 정수를 반환하거나 더 나쁜 결과를 반환하지 말고 정수 또는 다른 열거 형을 리턴 코드와 혼합하십시오.

  • 오류를 사람이 읽을 수있는 것으로 변환하는 기능을 제공합니다. 간단 할 수 있습니다. const-char * out 오류가 발생했습니다.

  • 이 아이디어는 멀티 스레드 사용을 약간 어렵게하지만 응용 프로그램 프로그래머가 전역 오류 콜백을 설정할 수 있다면 좋을 것입니다. 그렇게하면 버그 헌트 세션 중에 콜백에 중단 점을 넣을 수 있습니다.

도움이 되길 바랍니다.


답변

나는 두 가지 접근법을 모두 사용했으며 둘 다 잘 작동했습니다. 어느 것을 사용하든 항상이 원칙을 적용하려고합니다.

가능한 오류가 프로그래머 오류 인 경우 오류 코드를 반환하지 말고 함수 내에 assert를 사용하십시오.

입력을 검증하는 어설 션은 함수가 기대하는 것을 명확하게 전달하지만 너무 많은 오류 검사는 프로그램 논리를 모호하게 할 수 있습니다. 모든 다양한 오류 사례에 대해 수행 할 작업을 결정하면 실제로 설계가 복잡해질 수 있습니다. 프로그래머가 절대로 전달하지 않도록 주장 할 수 있다면 functionX가 널 포인터를 처리하는 방법을 알아내는 이유는 무엇입니까?


답변

CMU의 CERT에는 일반적인 C (및 C ++) 오류 처리 기술 각각을 사용할시기에 대한 권장 사항 이 포함 된 멋진 슬라이드 세트가 있습니다 . 가장 좋은 슬라이드 중 하나는이 의사 결정 트리입니다.

오류 처리 의사 결정 트리

이 flowcart에 대해 개인적으로 두 가지를 변경하려고합니다.

먼저, 때로는 객체가 오류를 나타 내기 위해 반환 값을 사용해야한다는 것을 분명히하고 싶습니다. 함수가 객체에서 데이터를 추출하지만 객체를 변경하지 않으면 객체 자체의 무결성이 위험하지 않으며 반환 값을 사용하여 오류를 나타내는 것이 더 적합합니다.

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

  1. C ++ 예외에는 매우 특정한 의미가 있습니다. 이러한 의미를 원하지 않으면 C ++ 예외가 나쁜 선택입니다. 예외가 발생하면 즉시 처리해야하며, 설계는 오류가 콜 스택을 몇 단계 풀어야하는 경우를 선호합니다.

  2. 예외를 던지는 C ++ 함수는 예외를 처리하기 위해 랩핑 할 수 없으며, 적어도 예외의 전체 비용을 지불하지 않으면 예외는 발생하지 않습니다. 오류 코드를 반환하는 함수는 C ++ 예외를 발생시키기 위해 랩핑되어보다 유연합니다. C ++ new은 던지지 않는 변형을 제공하여이 권리를 얻습니다.

  3. C ++ 예외는 상대적으로 비싸지 만이 단점은 예외를 현명하게 사용하는 프로그램에 비해 과장된 것입니다. 프로그램은 단순히 성능이 중요한 코드 경로에서 예외를 발생시키지 않아야합니다. 프로그램이 얼마나 빨리 오류를보고하고 종료 할 수 있는지는 중요하지 않습니다.

  4. 때로는 C ++ 예외를 사용할 수 없습니다. C ++ 구현에서 문자 그대로 사용할 수 없거나 코드 지침에서이를 금지합니다.


원래의 질문은 멀티 스레드 컨텍스트에 관한 것이기 때문에 로컬 오류 표시기 기술 ( SirDarius답변에 설명되어 있음 )이 원래 답변에서 과소 평가 되었다고 생각합니다 . 스레드 안전하고 호출자가 즉시 오류를 처리하지 않으며 오류를 설명하는 임의의 데이터를 묶을 수 있습니다. 단점은 객체에 의해 유지되어야하며 (어떻게 든 외부와 관련이 있다고 가정) 반환 코드보다 무시하기 쉽다는 것입니다.


답변

라이브러리를 만들 때마다 첫 번째 방법을 사용합니다. typedef’ed 열거 형을 리턴 코드로 사용하면 몇 가지 장점이 있습니다.

  • 함수가 배열과 같은 더 복잡한 출력을 반환하고 길이가 길면 임의의 구조를 만들어 반환 할 필요가 없습니다.

    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;
  • typedef’ed 열거 형을 사용하면 디버거에서 열거 형 이름을 볼 수도 있습니다. 이를 통해 지속적으로 헤더 파일을 참조 할 필요없이 디버깅이 쉬워집니다. 이 열거 형을 문자열로 변환하는 기능도 도움이됩니다.

사용 된 접근 방식에 관계없이 가장 중요한 문제는 일관된 것입니다. 이는 함수 및 인수 이름 지정, 인수 순서 및 오류 처리에 적용됩니다.


답변

setjmp를 사용하십시오 .

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

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

http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

#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;
}


답변

나는 개인적으로 이전 접근법을 선호합니다 (오류 표시기 반환).

필요한 경우 반환 결과는 오류가 발생했음을 나타내며 정확한 오류를 찾는 데 다른 함수가 사용됩니다.

getSize () 예제에서는 size가 항상 0 또는 양수 여야한다고 생각하므로 UNIX 시스템 호출과 마찬가지로 음수 결과를 반환하면 오류를 나타낼 수 있습니다.

포인터로 전달 된 오류 객체와 함께 후자의 접근법에 사용되는 라이브러리를 생각할 수 없습니다. stdio등은 모두 반환 값으로 이동합니다.


답변

프로그램을 작성할 때 초기화 중에 일반적으로 오류 처리를 위해 스레드를 분리하고 잠금을 포함하여 오류에 대한 특수 구조를 초기화합니다. 그런 다음 반환 값을 통해 오류를 감지하면 예외의 정보를 구조에 입력하고 SIGIO를 예외 처리 스레드로 보낸 다음 실행을 계속할 수 없는지 확인합니다. 할 수 없으면 SIGURG를 예외 스레드로 보내 프로그램을 정상적으로 중지합니다.