programing

C에서 오류 관리를 위해 goto를 사용할 수 있습니까?

javaba 2022. 7. 28. 23:16
반응형

C에서 오류 관리를 위해 goto를 사용할 수 있습니까?

이 질문은 사실 얼마 전 programming.reddit.com에서 있었던 흥미로운 토론의 결과입니다.기본적으로 다음과 같은 코드로 요약됩니다.

int foo(int bar)
{
    int return_value = 0;
    if (!do_something( bar )) {
        goto error_1;
    }
    if (!init_stuff( bar )) {
        goto error_2;
    }
    if (!prepare_stuff( bar )) {
        goto error_3;
    }
    return_value = do_the_thing( bar );
error_3:
    cleanup_3();
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

「 」의 goto가장 깨끗하고 효율적인 코드를 얻을 수 있는 최선의 방법인 것 같기도 하고, 적어도 그렇게 보이기도 합니다.Steve McConnell의 코드 컴플리트 인용:

goto는 리소스를 할당하고 해당 리소스에 대한 작업을 수행한 다음 리소스 할당을 해제하는 루틴에서 유용합니다.goto를 사용하면 코드의 한 부분을 청소할 수 있습니다.goto를 사용하면 오류를 검출한 각 위치에서 리소스 할당 해제를 잊어버릴 가능성을 줄일 수 있습니다.

이 어프로치의 또 다른 서포트는, 이 항의Linux Device Drivers(Linux 디바이스 드라이버)」를 참조해 주세요.

신은은 ?떻 ?? ????, 「 」의 입니까?goto 더 코드를 하지만 는 피하는 하십니까? 더 복잡하거나 덜 효율적인 코드를 생성하는 다른 방법을 선호하십니까?goto

FWIF님, 질문의 예에서 주신 오류 처리 관용구는 지금까지의 답변 중 어떤 대안보다 읽기 쉽고 이해하기 쉽다고 생각합니다.한편, 「 」는, 「 」, 「 」의 사이에goto는 일반적으로 나쁜 생각입니다.간단하고 균일한 방법으로 실시하면 에러 처리에 도움이 됩니다.'아주머니'라고 해도 '아주머니'라고 해도goto잘 정의되어 있고 다소 구조적인 방법으로 사용되고 있습니다.

일반적으로, 고토를 피하는 것은 좋은 생각이지만, 다이크스트라가 'GOTO Consided Hazardious'를 처음 썼을 때 만연했던 학대는 오늘날 대부분의 사람들의 마음에서 선택 사항으로 떠오르지도 않는다.

당신이 개략적으로 설명한 것은 오류 처리 문제에 대한 일반적인 해결책입니다. 신중하게 사용한다면 저는 괜찮습니다.

특정 예는 다음과 같이 단순화할 수 있습니다(스텝 1).

int foo(int bar)
{
    int return_value = 0;
    if (!do_something(bar)) {
        goto error_1;
    }
    if (!init_stuff(bar)) {
        goto error_2;
    }
    if (prepare_stuff(bar))
    {
        return_value = do_the_thing(bar);
        cleanup_3();
    }
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

프로세스의 속행:

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar))
    {   
        if (init_stuff(bar))
        {
            if (prepare_stuff(bar))
            {
                return_value = do_the_thing(bar);
                cleanup_3();
            }
            cleanup_2();
        }
        cleanup_1();
    }
    return return_value;
}

이것은, 제 생각에, 원래의 코드와 같습니다.이것은 원래 코드 자체가 매우 깨끗하고 잘 정리되어 있었기 때문에 특히 깨끗해 보입니다.코드 fragment는 (필요한 인수를 받아들이지만) 그렇게 깔끔하지 않은 경우가 많습니다.예를 들어 초기화(셋업) 루틴에 전달해야 하는 상태가 표시되는 것보다 많아 청소 루틴에도 전달해야 하는 상태가 많은 경우가 많습니다.

아무도 이 대안을 제안하지 않은 것에 놀랐습니다.이 문제를 해결하는 좋은 방법 중 하나는 변수를 사용하여 현재 상태를 추적하는 것입니다.이것은 사용 가능한 기술입니다.goto이치노기술과 한 것은 goto 내포되지 ifs.

기본적인 개념은 필요한 모든 정리 작업에 대해 그 값에서 정리할 필요가 있는지 여부를 확인할 수 있는 변수가 있다는 것입니다.

goto원래 질문의 코드에 더 가깝기 때문에 버전 우선입니다.

int foo(int bar)
{
    int return_value = 0;
    int something_done = 0;
    int stuff_inited = 0;
    int stuff_prepared = 0;


    /*
     * Prepare
     */
    if (do_something(bar)) {
        something_done = 1;
    } else {
        goto cleanup;
    }

    if (init_stuff(bar)) {
        stuff_inited = 1;
    } else {
        goto cleanup;
    }

    if (prepare_stuff(bar)) {
        stufF_prepared = 1;
    } else {
        goto cleanup;
    }

    /*
     * Do the thing
     */
    return_value = do_the_thing(bar);

    /*
     * Clean up
     */
cleanup:
    if (stuff_prepared) {
        unprepare_stuff();
    }

    if (stuff_inited) {
        uninit_stuff();
    }

    if (something_done) {
        undo_something();
    }

    return return_value;
}

중 수 를 들어, 함수의 순서를 변경할 수 있습니다. ★★★★★★★★★★★★★★★★★,switch되어 있는 방법, 변경되었을 , 「」의 순서가 변경되었을 경우, 「초기화」는 「초기화」의 순서로 되어 있습니다.switch애초에 초기화되지 않은 것을 정리하려고 하지 않도록 매우 신중하게 편집해야 합니다.

일부에서는 이 방법이 많은 변수를 추가한다고 주장할 수 있지만, 실제로는 기존 변수가 이미 필요한 상태를 추적하거나 추적할 수 있는 경우가 많습니다.를 들어, " " 가 ,prepare_stuff()는 실제로 콜입니다.malloc() 「」, 「」에 대해서open()반환된 포인터 또는 파일 기술자를 보유하는 변수를 사용할 수 있습니다.하다

int fd = -1;

....

fd = open(...);
if (fd == -1) {
    goto cleanup;
}

...

cleanup:

if (fd != -1) {
    close(fd);
}

변수를 하면 에러 상태를 회피할 수 .goto초기화가 필요할수록 점점 더 깊게 패이지 않고 완전히 청소할 수 있습니다.

int foo(int bar)
{
    int return_value = 0;
    int something_done = 0;
    int stuff_inited = 0;
    int stuff_prepared = 0;
    int oksofar = 1;


    /*
     * Prepare
     */
    if (oksofar) {  /* NB This "if" statement is optional (it always executes) but included for consistency */
        if (do_something(bar)) {
            something_done = 1;
        } else {
            oksofar = 0;
        }
    }

    if (oksofar) {
        if (init_stuff(bar)) {
            stuff_inited = 1;
        } else {
            oksofar = 0;
        }
    }

    if (oksofar) {
        if (prepare_stuff(bar)) {
            stuff_prepared = 1;
        } else {
            oksofar = 0;
        }
    }

    /*
     * Do the thing
     */
    if (oksofar) {
        return_value = do_the_thing(bar);
    }

    /*
     * Clean up
     */
    if (stuff_prepared) {
        unprepare_stuff();
    }

    if (stuff_inited) {
        uninit_stuff();
    }

    if (something_done) {
        undo_something();
    }

    return return_value;
}

이에 대한 잠재적인 비판도 있습니다.

  • 이 모든 "만약"이 퍼포먼스를 해치는 것은 아닐까? 합니다( 않으면 케이스를 한 경우 는 실패 합니다. 실패 시 대부분의 컴파일러가 실패 시퀀스를 최적화합니다.if (oksofar)청소 코드로의 싱글점프를 체크합니다(GCC는 확실히 체크합니다).어느 경우든, 에러 케이스는 퍼포먼스에 있어서 그다지 중요하지 않습니다.
  • 이은른른른른른른른른른른른른른른? 네, 단, ", ", " " 입니다.return_value하면 '변수'라는 을 할 수 .oksofar여기서 놀고 있어요.하면 두 에러를 .if " " in in : "

    int return_value = 0;
    
    if (!return_value) {
        return_value = do_something(bar);
    }
    
    if (!return_value) {
        return_value = init_stuff(bar);
    }
    
    if (!return_value) {
        return_value = prepare_stuff(bar);
    }
    

    이와 같은 코딩의 장점 중 하나는 일관성이란 원래 프로그래머가 반환값을 확인하는 것을 잊은 곳이 마치 아픈 엄지손가락처럼 튀어나와 버그를 훨씬 쉽게 찾을 수 있다는 것입니다.

이 문제를 해결하는 데 사용할 수 있는 스타일이 하나 더 있습니다.올바르게 사용하면 매우 깨끗하고 일관성 있는 코드를 만들 수 있습니다.그리고 다른 기술과 마찬가지로 악의적인 사람의 손에 의해 장황하고 혼란스러운 코드가 생성될 수 있습니다:-)

goto이치노이치노모든 goto를 통해 생성되는 추가 제어 경로를 알아두면 됩니다.당신의 코드와 그 유효성에 대해 추론하는 것이 어려워집니다.

FWIW, developer.apple.com 튜토리얼을 검색하면 오류 처리에 대한 goto 접근방식을 취하고 있습니다.

우리는 고토를 사용하지 않는다.반환값이 더 중요합니다.는 을 통해 .setjmp/longjmp아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 네.

GOTO는 유용하다.이것은 프로세서가 할 수 있는 일이기 때문에, 액세스 할 수 있는 것이 필요합니다.

때때로 당신은 당신의 기능에 약간의 무언가를 추가하고 싶을 때가 있는데, 당신은 그것을 쉽게 할 수 있습니다.시간을 절약할 수 있습니다.

init 함수의 "goto 문제"에 대한 또 다른 해결책으로 라이브러리를 사용합니다.
여기랑 여기 보세요.

goto 스테이트먼트에 도덕적으로 문제가 있는 것은 (void)* 포인터에 도덕적으로 문제가 있는 것과 마찬가지로 없습니다.

이 모든 것은 도구를 사용하는 방법에 달려 있습니다.제시된 (중요한) 케이스에서는 케이스 스테이트먼트에서도 같은 로직을 얻을 수 있습니다.단, 오버헤드는 커집니다.진짜 질문은 "속도 요구사항은?"입니다.

goto는 매우 빠릅니다. 특히 짧은 점프로 컴파일 할 수 있도록 주의한다면요.속도가 중시되는 애플리케이션에 최적.다른 애플리케이션의 경우 유지보수를 위해 if/else + 케이스로 오버헤드를 줄이는 것이 적절할 수 있습니다.

주의: goto는 애플리케이션을 파괴하는 것이 아니라 개발자가 애플리케이션을 파괴하는 것입니다.

업데이트: 다음은 사례 예시입니다.

int foo(int bar) { 
     int return_value = 0 ; 
     int failure_value = 0 ;

     if (!do_something(bar)) { 
          failure_value = 1; 
      } else if (!init_stuff(bar)) { 
          failure_value = 2; 
      } else if (prepare_stuff(bar)) { 
          return_value = do_the_thing(bar); 
          cleanup_3(); 
      } 

      switch (failure_value) { 
          case 2: cleanup_2(); 
          case 1: cleanup_1(); 
          default: break ; 
      } 
} 

일반적으로 코드 조각이 가장 명확하게 작성될 수 있는 것은goto일반적으로 바람직한 것보다 프로그램 흐름이 더 복잡할 수 있다는 증상입니다.다른 프로그램 구조를 이상한 방법으로 조합하여 사용하지 않도록 합니다.goto병을 치료하기보다는 증상을 치료하려고 할 것입니다.는 꼭 해야 한다, 라고 것 없이 실천할 수 .goto:

{을(를) 실행하다..조기 종료 시만 청소가 필요한 것을 1로 설정합니다.(오류)가 파손된 경우하다{..조기 종료 시 청소가 필요한 물건 2를 설정합니다.(오류)가 파손된 경우// ***** 이 행에 관한 텍스트 참조} while (0);
..정리물건2;
} while (0);
..청소1;

이 기능이 실패했을 있는 , '정리하다'는goto는 ""를 삽입하여 할 수 .return첫 번째 표적 라벨 바로 앞에 있습니다.하려면 , 를 할 필요가 있습니다.return「 」라고 *****.

에서는 '정리'를 .goto do/while(0)가 실질적으로 " ME"보다더 "LOOK AT ME입니다.break ★★★★★★★★★★★★★★★★★」do/while(0)''오류일 경우에만 '를 합니다.return함수의 그). 즉, "마지막에"가 합니다.return대상 라벨이 "루프" 종료 직전에 조건을 충족하는 것보다 훨씬 쉽게 충족됩니다.

BTW를 사용하는 중 는 'BTW'입니다.goto의 경우는, 「에러 처리」의 .switch스테이트먼트(여러 케이스의 코드가 같은 에러 코드를 공유하는 경우).는 여러 코드로 할 수 있을 경우가 , 는 다음과같이 것이한다.

재분석_PACKET:스위치(스위치[0]){케이스 PKT_이_조작:if (문제 상태)goto PACKET_ERROR;...THIS_OPERATION 처리부서지다케이스 PKT_That_Operation:if (문제 상태)goto PACKET_ERROR;...THIT_OPERATION 처리부서지다...케이스 PKT_PRESS_조건부if (syslog_length < 9)goto PACKET_ERROR;if (패킷을 포함하는 condition [4]){packet_length - = 5;memmove(memove, packet+5, packet_length);
재분석_PACK을 실행하다ET;}또 다른{packet [ 0 ]= PKT_CONDION_SKIPED;packet[4] = packet_length;packet_length = 5;packet_status = READY_TO_SEND;}부서지다...디폴트:{PACKET_ERROR:packet_error_count++;packet_length = 4;packet [ 0 ]= PKT_ERROR;packet_status = READY_TO_SEND;부서지다}}

「 」를 할 수 만,goto「」가 붙은 {handle_error(); break;} 을 할 수 , 을 사용할 수 있습니다.do/while(0) continue된 조건부 실행 사용하다보다 더 명확하다고 goto,PACKET_ERRORgoto PACKET_ERROR를 한 번 을 해당 복사본에 할 수 단, 를 사용하는 경우는, 「 the using the using the 」는 「 」를 사용합니다.goto는, 패킷을 조금 다르게 설정하는 장소를 보다 간단하게 확인할 수 있도록 합니다(예를 들면, 「조건부로 실행하지 않는」지시가 있는 경우).

저는 개인적으로 '10의 힘 - 안전 중요 코드 작성을 위한 10가지 규칙'의 추종자입니다.

그 텍스트에서 제가 goto에 대해 좋은 생각이라고 생각하는 부분을 작은 조각으로 첨부하겠습니다.


규칙: 모든 코드를 매우 단순한 제어 흐름 구조로 제한합니다. goto 문, setjmp 또는 longjmp 구문 및 직접 또는 간접 재귀는 사용하지 마십시오.

근거: 제어 플로우가 단순해지면 검증 기능이 강화되어 많은 경우 코드의 명확성이 향상됩니다.재귀의 추방은 아마도 여기서 가장 놀라운 것일 것이다.그러나 재귀가 없다면 코드아나라이저에 의해 이용될 수 있는 비순환 함수 호출 그래프가 보장되며, 실제로 경계되어야 하는 모든 실행이 경계임을 증명하는 데 직접 도움이 될 수 있습니다.(이 규칙에 따라 모든 기능에 단일 반환점을 설정할 필요는 없습니다.단, 제어 흐름도 단순해지는 경우가 많습니다.다만, 에러를 조기에 반환하는 것이 보다 간단한 해결책인 경우는 충분히 있습니다.)


goto 사용을 없애는 것은 나쁜 것 같지만:

만약 처음에 규칙이 엄격한 것처럼 보인다면, 그 규칙들은 말 그대로 여러분이 타는 비행기, 여러분이 살고 있는 곳에서 몇 마일 떨어진 원자력 발전소, 또는 우주 비행사를 궤도로 실어 나르는 우주선을 제어하는 데 사용되는 코드와 같은, 여러분의 삶이 정확성에 달려 있는 코드를 확인하는 것을 가능하게 한다는 것을 명심하세요.이 규칙은 자동차 안전벨트와 같은 역할을 합니다.처음에는 조금 불편할 수도 있지만, 잠시 후에는 사용하지 않는 것이 자연스러워지고 상상조차 할 수 없게 됩니다.

질문의 역순으로 정리하는 것이 대부분의 기능에서 가장 깔끔한 정리 방법이라는 것에 동의합니다.하지만 가끔은 기능을 정리하고 싶을 때도 있다는 것도 지적하고 싶습니다.이 경우 if ( 0 ) { label : } idi를 사용하여 청소 프로세스의 오른쪽 포인트로 이동합니다.

int decode ( char * path_in , char * path_out )
{
  FILE * in , * out ;
  code c ;
  int len ;
  int res = 0  ;
  if ( path_in == NULL )
    in = stdin ;
  else
    {
      if ( ( in = fopen ( path_in , "r" ) ) == NULL )
        goto error_open_file_in ;
    }
  if ( path_out == NULL )
    out = stdout ;
  else
    {
      if ( ( out = fopen ( path_out , "w" ) ) == NULL )
        goto error_open_file_out ;
    }

  if( read_code ( in , & c , & longueur ) )
    goto error_code_construction ;

  if ( decode_h ( in , c , out , longueur ) )
  goto error_decode ;

  if ( 0 ) { error_decode: res = 1 ;}
  free_code ( c ) ;
  if ( 0 ) { error_code_construction: res = 1 ; }
  if ( out != stdout ) fclose ( stdout ) ;
  if ( 0 ) { error_open_file_out: res = 1 ; }
  if ( in != stdin ) fclose ( in ) ;
  if ( 0 ) { error_open_file_in: res = 1 ; }
  return res ;
 }

가 보기엔 ★★★★★★★★★★★★★★★★★★★★★★.cleanup_3 불러주세요.cleanup_2.유사하게,cleanup_2정리하다 때마다 하는 것 cleanup_[n] , disclossiblecleanup_[n-1]를 들어, 「이러다」(예를 들어 「이러다」) 등cleanup_3하지 는 할 수 cleanup_2을 참조해 주십시오.)

그런 접근 방식을 사용할 경우 gotos 대신 청소 루틴을 호출하고 다시 돌아오면 됩니다.

goto다만, 어프로치가 잘못된 것도 나쁜 것도 아니고, 반드시 「가장 깨끗한」어프로치(IMHO)라고는 할 수 없습니다.

, 그 퍼포먼스는 그 퍼포먼스라고 할 수 있을 예요.goto솔루션이 최선입니다.다만, 퍼포먼스가 중요한 애플리케이션(디바이스 드라이버, 임베디드 디바이스 등)의 일부에 대해서만 관련성이 있다고 생각합니다.그렇지 않으면 코드 선명도보다 우선순위가 낮은 마이크로 최적화입니다.

나는 여기에 있는 질문이 주어진 코드에 관해 잘못된 것이라고 생각한다.

고려사항:

  1. do_something init_stuff() 및 prepare_stuff()는 false 또는 nero 중 하나를 반환하기 때문에 실패 여부를 알 수 있는 것으로 보입니다.
  2. foo()에서 직접 설정되는 상태는 없기 때문에 상태 설정은 이러한 기능의 책임인 것으로 보입니다.

따라서 do_something(), init_stuff() 및 prepare_stuff()는 자체 청소를 수행해야 합니다.do_something() 후에 정리하는 별도의 cleanup_1() 함수를 가지면 캡슐화의 철학이 깨집니다.디자인이 안 좋아요.

직접 청소를 했을 경우 foo()는 매우 간단합니다.

반면에.foo()가 실제로 해체할 필요가 있는 자체 상태를 작성한 경우 goto가 적절합니다.

다음은 제가 선호하는 사항입니다.

bool do_something(void **ptr1, void **ptr2)
{
    if (!ptr1 || !ptr2) {
        err("Missing arguments");
        return false;
    }
    bool ret = false;

    //Pointers must be initialized as NULL
    void *some_pointer = NULL, *another_pointer = NULL;

    if (allocate_some_stuff(&some_pointer) != STUFF_OK) {
        err("allocate_some_stuff step1 failed, abort");
        goto out;
    }
    if (allocate_some_stuff(&another_pointer) != STUFF_OK) {
        err("allocate_some_stuff step 2 failed, abort");
        goto out;
    }

    void *some_temporary_malloc = malloc(1000);

    //Do something with the data here
    info("do_something OK");

    ret = true;

    // Assign outputs only on success so we don't end up with
    // dangling pointers
    *ptr1 = some_pointer;
    *ptr2 = another_pointer;
out:
    if (!ret) {
        //We are returning an error, clean up everything
        //deallocate_some_stuff is a NO-OP if pointer is NULL
        deallocate_some_stuff(some_pointer);
        deallocate_some_stuff(another_pointer);
    }
    //this needs to be freed every time
    free(some_temporary_malloc);
    return ret;
}

하지만..."반대 패턴"을 사용하여 나중에 정적 인라인 함수의 모든 중첩 수준을 캡슐화하면 어떨까요?코드는 깨끗하고 최적이며(최적화가 활성화되어 있는 경우), goto는 사용되지 않습니다.간단히 말해서, 분열하고 정복하라.다음 예시는 다음과 같습니다.

static inline int foo_2(int bar)
{
    int return_value = 0;
    if ( prepare_stuff( bar ) ) {
        return_value = do_the_thing( bar );
    }
    cleanup_3();
    return return_value;
}

static inline int foo_1(int bar)
{
    int return_value = 0;
    if ( init_stuff( bar ) ) {
        return_value = foo_2(bar);
    }
    cleanup_2();
    return return_value;
}

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar)) {
        return_value = foo_1(bar);
    }
    cleanup_1();
    return return_value;
}

공간적인 측면에서는 스택에 변수를 3배 만들고 있는데, 이는 좋지 않지만, 이 간단한 예에서 변수를 스택에서 삭제하고 레지스터를 사용하면 사라집니다. 은 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」gcc -S -O2 test.c이하에 기재되어 있습니다.

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _foo                    ## -- Begin function foo
    .p2align    4, 0x90
_foo:                                   ## @foo
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    pushq   %r14
    pushq   %rbx
    .cfi_offset %rbx, -32
    .cfi_offset %r14, -24
    movl    %edi, %ebx
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    callq   _do_something
    testl   %eax, %eax
    je  LBB0_6
## %bb.1:
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _init_stuff
    testl   %eax, %eax
    je  LBB0_5
## %bb.2:
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _prepare_stuff
    testl   %eax, %eax
    je  LBB0_4
## %bb.3:
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _do_the_thing
    movl    %eax, %r14d
LBB0_4:
    xorl    %eax, %eax
    callq   _cleanup_3
LBB0_5:
    xorl    %eax, %eax
    callq   _cleanup_2
LBB0_6:
    xorl    %eax, %eax
    callq   _cleanup_1
    movl    %r14d, %eax
    popq    %rbx
    popq    %r14
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

예, C의 예외에 대한 유효하고 최선의 프랙티스입니다.모든 언어의 오류 처리 메커니즘은 오류에서 처리로 이동하며 레이블로 이동합니다.그러나 실행 흐름에서 goto 뒤에 동일한 범위에 라벨을 배치하는 것을 고려해 보십시오.

다음 예에서 설명하는 기술을 사용하는 것이 좋습니다.

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=module

언급URL : https://stackoverflow.com/questions/788903/valid-use-of-goto-for-error-management-in-c

반응형