programing

스트레이트 C 프로그램에서 오류 처리에 좋은 숙어가 있습니까?

javajsp 2023. 10. 21. 09:57

스트레이트 C 프로그램에서 오류 처리에 좋은 숙어가 있습니까?

다시 C 작업을 시작하는 중입니다.

많은 기능이 다음과 같습니다.

int err = do_something(arg1, arg2, arg3, &result);

결과는 함수에 의해 채워지고 반환 값은 통화 상태가 됩니다.

부정적인 측면은 이런 순진한 것을 얻게 된다는 것입니다.

int err = func1(...);
if (!err) {
    err = func2(...);
    if (!err) {
        err = func3(...);
    }
}
return err;

매크로를 할 수 있을 것 같네요.

#define ERR(x) if (!err) { err = (x) }
int err = 0;
ERR(func1(...));
ERR(func2(...));
ERR(func3(...));
return err;

그러나 이는 기능 호출을 연결하는 경우와 다른 작업을 수행하는 경우에만 작동합니다.

분명히 자바, C#, C++에는 이러한 종류의 것들에 매우 적합한 예외들이 있습니다.

요즘 C 프로그램에서 다른 사람들은 무엇을 하고 다른 사람들은 어떻게 오류를 처리하는지 궁금합니다.

마지막에 공개해야 할 리소스가 있는 경우에는 기존의 신뢰도가 높은 경우도 있습니다.goto편리할 수 있습니다!

int
major_func(size_t len)
{
    int err;
    char *buf;

    buf = malloc(len);

    if (err = minor_func1(buf))
        goto major_func_end;
    if (err = minor_func2(buf))
        goto major_func_end;
    if (err = minor_func3(buf))
        goto major_func_end;

major_func_end:
    free(buf);
    return err;
}

일반적인 두 가지 패턴:

int major_func()
{
    int err = 0;

    if (err = minor_func1()) return err;
    if (err = minor_func2()) return err;
    if (err = minor_func3()) return err;

    return 0;
}

int other_idea()
{
    int err = minor_func1();
    if (!err)
        err = minor_func2();
    if (!err)
        err = minor_func3();
    return err;            
}

void main_func()
{
    int err = major_func();
    if (err)
    {
        show_err();
        return;
    }
    happy_happy_joy_joy();

    err = other_idea();
    if (err)
    {
        show_err();
        return;
    }
    happy_happy_joy_joy();
}

당신은 뭐하고 있어요?else진술? 도 아니라면, 이걸 시도해보세요아무것도 아니라면 다음을 시도하십시오.

int err = func1(...);
if (err) {
    return err;
}

err = func2(...);
if (err) {
    return err;
}

err = func3(...);

return err;

이렇게 하면 전체 기능을 단락시킬 수 있고, 다음 기능 호출에도 문제가 되지 않습니다.

편집

돌아가서 다시 읽어보면, 당신이 당신의 일을 어떻게 하든 상관없다는 것을 알게 됩니다.else진술들.그런 종류의 코드는 즉시 쉽게 갈 수 있습니다.if토막토막

오류 코드가 부울일 경우 아래의 간단한 코드를 사용해 보십시오.

return func1() && func2() && func3()

OpenGL이 취한 한 가지 접근법은 함수에서 오류를 아예 반환하지 않고 함수 호출 후 검사할 수 있는 오류 상태를 나타내는 것입니다.이 접근 방식의 한 가지 좋은 점은 오류 코드가 아닌 다른 것을 실제로 반환하고 싶은 함수가 있을 때, 같은 방식으로 오류를 처리할 수 있다는 것입니다.또 하나 좋은 점은 사용자가 여러 기능을 호출하고 모두 성공해야만 성공하려면 x번 호출 후 오류를 확인할 수 있다는 점입니다.

/* call a number of functions which may error.. */
glMatrixMode(GL_MODELVIEW);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);

/* ...check for errors */
if ((error = glGetError()) != GL_NO_ERROR) {
    if (error == GL_INVALID_VALUE)
        printf("error: invalid value creating view");
    else if (error == GL_INVALID_OPERATION)
        printf("error: invalid operation creating view");
    else if (error == GL_OUT_OF_MEMORY)
        printf("error: out of memory creating view");
}

다른 사람들은 좋은 아이디어를 제안했습니다.여기에 내가 본 관용구들이 있습니다.

int err;
...
err = foo(...);
if (err)
    return err;
...

당신은 이것을 매크로로 처리할 수 있습니다.

#define dERR int err=0
#define CALL err = 
#define CHECK do { if (err) return err } while(0)
...
void my_func(void) {
   dERR;
   ...
   CALL foo(...);
   CHECK;

아니면 정말 의욕적이라면, CALL과 CHECK를 만지작거리면 다음과 같이 사용할 수 있습니다.

CALL foo(...) CHECK;

아니면

CALL( foo(...) );

--

종종 출구에서 정리를 해야 하는 함수(예: 메모리 사용 가능)는 다음과 같이 기록됩니다.

int do_something_complicated(...) {
    ...

    err = first_thing();
    if (err)
       goto err_out;

    buffer = malloc(...);
    if (buffer == NULL)
        goto err_out

    err = another_complicated(...);
    if (err)
        goto err_out_free;

    ...

   err_out_free:
    free(buffer);
   err_out:
    return err; /* err might be zero */
}

이 패턴을 사용하거나 매크로를 사용하여 단순화할 수 있습니다.

--

마지막으로, 만약 당신이 정말/동기를 느끼고 있다면 setjmp/longjmp를 사용할 수 있습니다.

int main(int argc, char *argv[]) {
    jmp_buf on_error;
    int err;
    if (err = setjmp(on_error)) {
        /* error occurred, error code in err */
        return 1;
    } else {
        actual_code(..., on_error);
        return 0;
    }
}
void actual_code(..., jmp_buf on_error) {
    ...
    if (err)
        longjmp(on_error, err);
}

기본적으로 새로운 jmp_buf의 선언과 try block 설정으로 setjmp 함수를 설정합니다.setjmp가 0이 아닌 값을 반환하는 경우가 당신의 캐치이고 longjmp를 호출하는 것이 당신의 스로우입니다.중첩 핸들러를 원할 경우(예: 오류 신호를 보내기 전에 물건을 비워야 할 경우) jmp_buf를 전달하면서 작성했습니다. 필요하지 않다면 언제든지 errand the jmp_buf를 글로벌로 선언하십시오.

또는 매크로를 사용하여 단순히 전달되는 논쟁을 수행할 수도 있습니다.Perl의 구현 방식을 제안합니다.

#define pERR jmp_buf _err_handler
#define aERR _err_handler
#define HANDLE_ERRORS do { jmp_buf _err_handler; int err = setjmp(_err_handler);
#define END_HANDLE while(0)
#define TRY if (! err)
#define CATCH else
#define THROW(e) longjmp(_err_handler, e)

void always_fails(pERR, int other_arg) {
    THROW(42);
}
void does_some_stuff(pERR) {
    normal_call(aERR);
    HANDLE_ERRORS
      TRY {
        always_fails(aERR, 23);
      } CATCH {
        /* err is 42 */
      }
    END_HANDLE;
}
int main(int argc, char *argv[]) {
    HANDLE_ERRORS
      TRY {
        does_some_stuff(aERR);
        return 0;
      } CATCH {
        return err;
      }
    DONE_ERRORS;
}

--

휴, 난 끝났어요 (이상한 예시들은 검증되지 않았습니다.일부 세부 정보가 잘못되었을 수 있습니다.)

그리고 이제 완전히 다른 것을 위해...

또 다른 접근 방식은 오류 정보를 포함하는 구조를 사용하는 것입니다. 예를 들어:

struct ErrorInfo
{
    int errorCode;
    char *errorMessage;
#if DEBUG
    char *functionName;
    int lineNumber;
#endif
}

이를 사용하는 가장 좋은 방법은 메서드의 결과를 반환 코드로 반환하고(예: "FALSE for failed", "failed" 또는 "failed"일 경우 파일 포인터 또는 NULL", "buffer의 크기 또는 failed" 등), 호출된 함수가 실패할 경우 입력할 매개 변수로 ErrorInfo를 전달하는 것입니다.

이것은 풍부한 오류 보고를 제공합니다. 메소드가 실패할 경우 단순한 오류 코드 이상(예: 오류 메시지, 코드 라인 및 오류 파일 등)을 입력할 수 있습니다.구조라는 점에서 좋은 점은 나중에 유용한 무언가를 생각한다면 그냥 추가할 수 있다는 것입니다. 예를 들어 위의 구조라면 디버그 빌드에서 오류(파일/라인)의 위치를 포함하도록 허용했습니다.클라이언트 코드를 변경할 필요 없이 언제든지 전체 콜 스택 덤프를 추가할 수 있습니다.

전역 함수를 사용하여 ErrorInfo를 입력하여 오류 반환을 깨끗하게 관리할 수 있으며 구조를 업데이트하여 더 많은 정보를 쉽게 제공할 수 있습니다.

if (error)
{
    Error(pErrorInfo, 123, "It failed");
    return(FALSE);
}

...그리고 FALSE, 0 또는 NULL을 반환하는 이 함수의 변형을 사용하여 대부분의 오류 반환을 한 줄로 표시할 수 있습니다.

if (error)
    return(ErrorNull(pErrorInfo, 123, "It failed"));

이를 통해 다른 언어의 Exception 클래스(발신자가 여전히 오류를 처리해야 함에도 불구하고)의 많은 이점을 얻을 수 있습니다. 발신자는 오류 코드를 확인해야 하고 일찍 돌아가야 할 수도 있지만, 아무 것도 할 수 없거나 거의 할 수 없으며 한 사람이 처리하기를 원할 때까지 오류가 일련의 호출 방법을 다시 전파하도록 허용합니다.그것은 마치 예외에 가깝습니다.

또한 "InnerException"과 같은 일련의 오류 보고서를 생성하기 위해 다음과 같은 작업을 수행할 수 있습니다.

struct ErrorInfo
{
    int errorCode;
    char *errorMessage;
    ...
    ErrorInfo *pInnerError;    // Pointer to previous error that may have led to this one
}

그런 다음 호출한 함수에서 오류를 "포착"하면 새로운 상위 수준의 오류 설명을 생성하고 이러한 오류 체인을 반환할 수 있습니다. 예를 들어 "마우스 속도가 기본값으로 되돌아갑니다." (이유) "기본 설정 블록 '마우스프렙'을 찾을 수 없습니다." (이유) "XML 리더 실패" (이유) "파일을 찾을 수 없습니다.

예.

FILE *OpenFile(char *filename, ErrorInfo *pErrorInfo)
{
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't open file"));

    return(fp);
}

XmlElement *ReadPreferenceXml(ErrorInfo *pErrorInfo)
{
    if (OpenFile("prefs.xml", pErrorInfo) == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't read pref"));
    ...
}

char *ReadPreference(char *prefName, ErrorInfo *pErrorInfo)
{
    XmlElement *pXml = ReadPreferenceXml(pErrorInfo);
    if (pXml == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't read pref"));
    ...
}

DirectX가 HRESULT에 대해 무엇을 했는지 확인해보셔야 합니다. 기본적으로 이렇습니다.예외가 생긴 데는 이유가 있습니다.또는 Win32에서 실행하면 C 프로그램에서 실행되는 SEH가 있습니다.

당신은 정말 바보같이 행동할 수 있습니다.

void step_1(int a, int b, int c, void (*step_2)(int), void (*err)(void *) ) {
     if (!c) {
         err("c was 0");
     } else {
         int r = a + b/c;
         step_2(r);
     }
}

이것은 실제로 여러분이 원하는 것은 아니지만, 얼마나 많은 기능적 프로그래밍 언어가 사용되는지, 그리고 최적화를 위해 코드를 어떻게 모델링하는지에 대한 것입니다.

제가 최근에 본 것은 이 우상입니다.

int err;
do 
{
  err = func1 (...);
  if (!err) break;

  err = func2 (...);
  if (!err) break;

  err = func3 (...);
  if (!err) break;

  /* add more calls here */

} while (0);

if (err)
{
  /* handle the error here */
  return E_ERROR; /* or something else */
}
 else 
{
  return E_SUCCESS;
}

프로 인수:

고투(시간(0)/브레이크 조합)를 방지합니다.왜 이런 짓을 하고 싶으십니까?사이클로매틱 복잡도를 낮추고 대부분의 정적 코드 분석기 검사(MISRA 누구?)를 통과합니다.순환적 복잡성에 대한 테스트를 받는 프로젝트의 경우 모든 초기화 작업을 함께 유지하기 때문에 신이 보낸 것입니다.

대비 인수:

Do/while 루프 컨스트럭트의 의미는 명확하지 않습니다. 왜냐하면 루프 컨스트럭트는 저렴한 교체로 사용되기 때문입니다. 그리고 이것은 루프 테일에서만 볼 수 있기 때문입니다.저는 이 구조물이 처음으로 많은 "WTF" 순간들을 야기할 것이라고 확신합니다.

코드가 필요한 방식으로 작성된 이유를 설명하기 위해서는 최소한 코멘트가 필요합니다.

다음은 IBM Unix 기사 시리즈의 꽤 유익한 기사와 테스트 파일입니다.

오류: UNIX 프로그램의 오류

표준 오류 메커니즘으로 작업

https://www.ibm.com/developerworks/aix/library/au-errnovariable/

종료 코드를 구현하는 방법의 또 다른 좋은 예는 컬(man 1 curl)의 소스 코드입니다.

구체적인 맥락을 가지고 일을 하신다면, 다음과 같은 패턴이 매우 좋다고 생각합니다.기본적인 생각은 오류가 설정된 상태에서의 작업은 노옵스이기 때문에 오류 확인은 편리한 시간으로 미룰 수 있다는 것입니다!

구체적인 예: 역직렬화 맥락.모든 요소의 디코딩은 실패할 수 있지만 모든 요소가 오류 검사 없이 기능이 계속될 수 있습니다.decode_*직렬화 레코드가 오류 상태일 때 함수는 no-ops입니다.삽입하는 것은 편의성, 기회 또는 최적화의 문제입니다.decode_has_error. 아래 예에서는 오류 검사가 없으며, 발신자가 처리합니다.

void list_decode(struct serialization_record *rec,                       
                 struct list *list,                                     
                 void *(*child_decode)(struct serialization_record *)) {
    uint32_t length;                                                             
    decode_begin(rec, TAG);                                  
    decode_uint32(rec, &length);                                          
    for (uint32_t i = 0; i < length; i++) {                                
        list_append(list, child_decode(rec));
    }                                                                        
    decode_end(rec, TAG);
}

언급URL : https://stackoverflow.com/questions/2789987/any-good-idioms-for-error-handling-in-straight-c-programs