C에서 goto의 적절한 사용 사례입니까?


59

"토론, 논쟁, 여론 조사, 또는 확장 된 토론을 모방하고 싶지는 않지만"C를 처음 사용하기 때문에 언어에 사용되는 일반적인 패턴에 대해 더 많은 통찰력을 얻고 싶어하기 때문에이 질문을하는 것이 주저합니다.

나는 최근에 goto명령에 대한 약간의 불쾌감을 들었지만 최근에는 그에 대한 적절한 사용 사례를 발견했습니다.

다음과 같은 코드 :

error = function_that_could_fail_1();
if (!error) {
    error = function_that_could_fail_2();
    if (!error) {
        error = function_that_could_fail_3();
        ...to the n-th tab level!
    } else {
        // deal with error, clean up, and return error code
    }
} else {
    // deal with error, clean up, and return error code
}

정리 부분이 모두 매우 유사한 경우 다음과 같이 조금 더 예쁘게 작성할 수 있습니다 (내 의견?).

error = function_that_could_fail_1();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
    goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code

이것이 gotoC에서 일반적이거나 수용 가능한 사용 사례 입니까? 이것을 수행하는 다른 / 더 나은 방법이 있습니까?


3
이 질문 (및 내 대답 ) 도 참조하십시오 .
Keith Thompson

2
설탕 코팅 이상으로 컴파일러는 정리 및 오류 검사를 보장하는 반면 C에서는 수동으로 수행해야 DRY를 위반하고 어떤 곳에서는 오류 코드를 확인하기에 너무 게 으르거나 잘못된 레이블 또는 이와 유사한 것으로 이동해야합니다.
DeadMG

3
@Izkata : 아니요, 요점은 예외가 발생할 때 컴파일러 가 암시 적으로 호출 하는 함수 (소멸자와 같은)를 갖는 것입니다.
DeadMG

6
@DeadMG : 다른 언어를 사용하는 것이 항상 옵션은 아닙니다.
Benjamin Kloster 2016 년

13
적절한 레이블 이름을 사용한다고 생각하면 예제가 꽤 goto hell;
괜찮아

답변:


48

goto문 (및 해당 라벨) 흐름 제어이다 프리미티브 (명령문의 실행 조건과 함께). 즉, 프로그램 흐름 제어 네트워크를 구성 할 수 있다는 의미입니다. 플로우 차트의 노드 간 화살표를 모델링하는 것으로 생각할 수 있습니다.

이 중 일부는 직접 선형 흐름이있는 경우 즉시 최적화 할 수 있습니다 (기본 문장 시퀀스 만 사용). 다른 패턴은 사용 가능한 구조화 된 프로그래밍 구성으로 가장 잘 대체됩니다. 그것이 보이는 경우 while루프, 용도 while루프를 확인? 구조화 된 프로그래밍 패턴은 적어도 확실히있는 가능성 의 혼란보다는 의도를 명확하게 goto진술.

그러나 C는 가능한 모든 구조화 된 프로그래밍 구조를 포함하지 않습니다. (모든 관련 항목이 아직 발견되었다는 것은 확실하지 않습니다. 지금 발견 속도가 느리지 만 모든 것이 발견되었다고 말하고 주저하고 있습니다.) 우리가 알고있는 것 중 C에는 try/ catch/ finally구조 (및 예외도). 또한 다중 레벨 break루프에서 부족 합니다. 이것들은 goto구현에 사용될 수있는 것들입니다 . 다른 스키마를 사용하여 이러한 작업을 수행 할 수도 있습니다. C에는 충분한goto프리미티브 (primitive)-그러나 종종 플래그 변수와 훨씬 더 복잡한 루프 또는 가드 조건을 생성해야합니다. 데이터 분석으로 제어 분석의 얽힘을 증가 시키면 프로그램을 전체적으로 이해하기가 더 어려워집니다. 그것은 또한 더 어려워 컴파일러가 최적화 할 수와 CPU가 빠르게 실행할 수 있도록합니다 (대부분의 흐름 제어 구조 - 그리고 확실히 goto 매우 저렴합니다 -).

따라서 goto필요한 경우 가 아니라면 사용해서는 안되며 , 존재하고 필요할 수도 있다는 것을 알고 있어야하며 , 필요한 경우 너무 나쁘지 않아야합니다. 필요한 경우의 예는 호출 된 함수가 오류 조건을 리턴 할 때 자원 할당 해제입니다. ( 그렇지만 try/ finally.) 쓸 goto수는 있지만 그렇게 하지 않으면 유지 관리 문제와 같은 자체 단점이있을 수 있습니다. 사례의 예 :

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

코드는 더 짧을 수 있지만 요점을 설명하기에 충분합니다.


4
1 : C 고토 기술적으로 "필요 없다"결코에서 - 그것을 할 수있는 방법이 항상있다, 그것은 MISRA C.에서 고토 모양의 사용을위한 가이드 라인의 강력한 들어 ..... 지저분
mattnz

1
당신은 선호 try/catch/finallygoto사용 가능 스파게티 코드의 유사, 아직 더 보급 (이 여러 기능 / 모듈에 퍼졌다 수) 형태에도 불구하고 try/catch/finally?
자폐증

65

예.

예를 들어 리눅스 커널에서 사용됩니다. 다음 은 거의 10 년 전의 글 이 끝날 무렵에 대담한 이메일 입니다.

보낸 사람 : Robert Love
제목 : Re : 2.6.0-test *의 기회가 있습니까?
날짜 : 2003 년 1 월 12 일 17:58:06 -0500 2003 년 1 월 12 일

17시 22 분 일요일, Rob Wilkens는 다음과 같이 썼습니다.

"goto를 사용하지 마십시오"라고 말하고 대신 "cleanup_lock"함수를 사용하여 모든 return 문 앞에 추가하십시오. 부담이되어서는 안됩니다. 예, 개발자가 조금 더 열심히 일하도록 요구하고 있지만 최종 결과는 더 나은 코드입니다.

아니, 그것은 거칠고 커널을 부 풀린다 . 종료 코드를 한 번에 끝내는 것과 달리 N 오류 경로에 대한 정크를 인라인합니다. 캐시 풋 프린트가 핵심이며 방금 삭제했습니다.

읽기도 쉽지 않습니다.

마지막 주장으로, 우리는 일반적인 스택처럼 바람을 불고 풀 수 없습니다 .

        do A
        if (error)
            goto out_a;
        do B
        if (error)
            goto out_b;
        do C
        if (error)
            goto out_c;
        goto out;
        out_c:
        undo C
        out_b:
        undo B:
        out_a:
        undo A
        out:
        return ret;

이제 그만 해요

로버트 러브

즉, goto 사용에 익숙해지면 스파게티 코드를 작성하지 못하게하려면 많은 훈련이 필요합니다. 따라서 속도와 낮은 메모리 공간 (커널 또는 임베디드 시스템과 같은)이 필요한 것을 작성하지 않는 한 실제로 첫 번째 goto를 작성 하기 전에 생각해보십시오 .


21
커널은 원시 속도와 가독성에 대한 우선 순위에 관한 비 커널 프로그램과 다릅니다. 다시 말해, 그들은 이미 프로파일 링되었으며 goto를 사용하여 속도에 맞게 코드를 최적화해야한다는 것을 알았습니다.

11
실제로 스택 위로 밀지 않고 스택 풀기를 사용하여 오류에 대한 정리 처리! 이것은 goto의 멋진 사용법입니다.
mike30

1
@ user1249, Rubbish, {라이브러리, 커널} 코드를 사용하는 모든 {과거, 기존, 미래} 앱을 프로파일 링 할 수 없습니다. 당신은 단순히 빨리해야합니다.
Pacerier

1
무관 : 사람들이 메일 링리스트를 사용하여 대규모 프로젝트를 제외하고 무엇이든 할 수있는 방법에 놀랐습니다. 그냥 ... 원시적입니다. 사람들은 메시지의 소방서와 어떻게 들어 옵니까?!
Alexander

2
나는 중재에 대해 걱정하지 않습니다. 만약 누군가 인터넷에있는 어떤 멍청한 사람에 의해 빠져 나올 정도로 부드럽다면, 당신의 프로젝트는 아마도 그들 없이도 더 나을 것입니다. 예를 들어 들어오는 메시지의 방해를 막을 수있는 비 실용성과 따옴표를 추적하기위한 툴링을 사용하여 자연스럽게 뒤에서 논의 할 수있는 방법에 대해 더 걱정하고 있습니다.
Alexander

14

내 의견으로는, 게시 한 코드는의 올바른 사용의 예입니다. goto왜냐하면 아래로 뛰어 내리고 원시 예외 처리기처럼 사용하기 때문입니다.

그러나 오래된 goto 토론으로 인해 프로그래머는 goto약 40 년 동안 회피 해 왔 으므로 goto로 코드를 읽는 데 익숙하지 않습니다. 이것이 바로 goto를 피하는 유효한 이유입니다. 단순히 표준이 아닙니다.

C 프로그래머가 더 쉽게 읽을 수있는 코드를 다시 작성했을 것입니다.

Error some_func (void)
{
  Error error;
  type_t* resource = malloc(...);

  error = some_other_func (resource);

  free (resource);

  /* error handling can be placed here, or it can be returned to the caller */

  return error;
}


Error some_other_func (type_t* resource)  // inline if needed
{
  error = function_that_could_fail_1();
  if(error)
  {
    return error;
  }

  /* ... */

  error = function_that_could_fail_2();
  if(error)
  {
    return error;
  }

  /* ... */

  return ok;
}

이 디자인의 장점 :

  • 실제 작업을 수행하는 함수는 데이터 할당과 같이 알고리즘과 관련이없는 작업에 신경 쓸 필요가 없습니다.
  • 코드는 C 프로그래머에게 덜 외로워 보일 것입니다. 왜냐하면 그들은 프로그래머와 레이블을 두려워하기 때문입니다.
  • 알고리즘을 수행하는 함수 외부의 동일한 지점에서 오류 처리 및 할당 해제를 중앙 집중화 할 수 있습니다. 함수가 자체 결과를 처리하는 것은 의미가 없습니다.


9

Java에서는 다음과 같이합니다.

makeCalls:  {
    error = function_that_could_fail_1();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_2();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_3();
    if (error) {
        break makeCalls;
    }
    ...
    return 0;  // No error code.
}
// deal with error if it exists, clean up
// return error code

나는 이것을 많이 사용합니다. 내가 싫어하는 goto대부분의 다른 C 스타일 언어에서는 코드를 사용합니다. 다른 좋은 방법은 없습니다. (중첩 루프에서 점프하는 것은 비슷한 경우입니다 .Java에서는 레이블이 break있고 다른 곳에서는을 사용합니다 goto.)


3
그것은 깔끔한 제어 구조입니다.
Bryan Boettcher

4
정말 흥미 롭습니다. 나는 일반적으로 이것을 위해 try / catch / finally 구조를 사용한다고 생각할 것입니다 (깨지기 대신 예외 던지기).
Robz

5
그것은 실제로 읽을 수 없습니다 (적어도 나를 위해). 있으면 예외가 훨씬 낫습니다.
m3th0dman

1
@ m3th0dman이 특정 예 (오류 처리)에서 동의합니다. 그러나이 관용구가 유용 할 수있는 다른 (예외가 아닌) 사례가 있습니다.
Konrad Rudolph

1
예외는 비싸므로 오류, 스택 추적 및 훨씬 더 많은 정크를 생성해야합니다. 이 레이블 구분은 점검 루프를 완전히 종료합니다. 하나는 메모리와 속도에 관심이 없다면 내가 관심을 갖는 모든 예외를 사용하십시오.
Tschallacka

8

내가 생각 입니다 괜찮은 유스 케이스하지만, 경우에 "오류"부울 값에 불과하다, 당신이 원하는 것을 달성하기 위해 다른 방법이있다 :

error = function_that_could_fail_1();
error = error || function_that_could_fail_2();
error = error || function_that_could_fail_3();
if(error)
{
     // do cleanup
}

부울 연산자의 단락 평가를 사용합니다. 이것이 "더 나은"경우, 당신의 개인적인 취향과 그 관용구에 익숙한 방법에 달려 있습니다.


1
이것의 문제는 error'모든 수술실에서 가치가 무의미해질 수 있다는 것입니다.
James

@ 제임스 : 귀하의 의견으로 내 답변을 편집했습니다
Doc Brown

1
충분하지 않습니다. 첫 번째 기능 중에 오류가 발생하면 두 번째 또는 세 번째 기능을 실행하고 싶지 않습니다.
Robz

2
함께하면 짧은 손의 평가는 의미 단락 회로 평가, 이는 비트의 사용 또는 대신 논리적으로 OR 여기에 무슨 짓을 정확히하지 않습니다.
Christian Rau

1
@ChristianRau : 고마워요, 내 대답을 적절히 편집했습니다
Doc Brown

6

리눅스 스타일 가이드는 goto예제와 일치하는를 사용해야하는 특별한 이유를 제공 합니다.

https://www.kernel.org/doc/Documentation/process/coding-style.rst

gotos를 사용하는 이유는 다음과 같습니다.

  • 무조건 진술은 이해하고 따르기가 더 쉽다
  • 중첩이 줄어 듭니다
  • 수정시 개별 종료점을 업데이트하지 않아 오류 발생
  • 중복 코드를 최적화하기 위해 컴파일러 작업을 절약합니다.)

면책 조항 나는 내 일을 공유해서는 안됩니다. 여기 예제는 약간 고려되어 있으므로 곰이 나와 함께 견뎌주십시오.

메모리 관리에 좋습니다. 나는 최근에 동적으로 메모리를 할당 한 코드 (예 char *: 함수에 의해 반환 된 코드)를 연구했습니다. 경로를보고 경로의 토큰을 구문 분석하여 경로가 유효한지 확인하는 함수입니다.

tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        free(var1);
        free(var2);
        ...
        free(varN);
        return 1;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            free(var1);
            free(var2);
            ...
            free(varN);
            return 1;
        } else {
            free(var1);
            free(var2);
            ...
            free(varN);
            return 0;
        }
    }
    token = strtok(NULL,delim);
}

free(var1);
free(var2);
...
free(varN);
return 1;

이제 나에게 다음 코드를 추가 해야하는 경우 훨씬 훌륭하고 유지 관리가 쉽습니다 varNplus1.

int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        retval = 1;
        goto out_free;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            retval = 1;
            goto out_free;
        } else {
            retval = 0;
            goto out_free;
        }
    }
    token = strtok(NULL,delim);
}

out_free:
free(var1);
free(var2);
...
free(varN);
return retval;

이제 코드에는 N과 관련하여 다른 문제가 있습니다. 즉, N은 10 이상이고 함수는 450 개가 넘었으며 10 개 수준의 중첩이 있습니다.

그러나 나는 관리자에게 리팩토링을 제안했는데, 내가 한 일이 이제는 짧고 기능은 모두 리눅스 스타일입니다.

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 == NULL ){
        retval = 0;
        goto out;
    }

    if( isValid(var1) ){
         retval = some_function(var1);
         goto out_free;
    }

    if( isGood(var1) ){
         retval = 0;
         goto out_free;
    }

out_free:
    free(var1);
out:
    return retval;
}

우리가 gotos 없이 동등한 것을 고려한다면 :

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 != NULL ){

       if( isValid(var1) ){
            retval = some_function(var1);
       } else {
          if( isGood(var1) ){
               retval = 0;
          }
       }
       free(var1);

    } else {
       retval = 0;
    }

    return retval;
}

나에게, 첫 번째 경우, 첫 번째 함수 NULL가을 반환 하면 여기서 나가서 반환한다는 것이 분명합니다 0. 두 번째 경우에는 if가 전체 함수를 포함하는지 확인하기 위해 아래로 스크롤해야합니다. 첫 번째는 이것이 나에게 문체 적으로 (이름 " out") 표시하고 두 번째는 구문 적으로 표시합니다. 첫 번째는 여전히 더 분명합니다.

또한 free()함수 끝에 선언문을 사용하는 것이 좋습니다. 그것은 내 경험상 free()함수의 중간에있는 문장이 나쁜 냄새를 맡고 서브 루틴을 만들어야한다는 것을 나타 내기 때문입니다. 이 경우, 나는 var1내 기능으로 만들었고 free()서브 루틴에서 그것을 할 수 없었 습니다. 그러나 그것이 goto out_freegoto out 스타일이 그렇게 실용적인 이유 입니다.

프로그래머 goto가 사악 하다고 믿어야한다고 생각합니다 . 그런 다음 충분히 성숙 해지면 Linux 소스 코드를 탐색하고 Linux 스타일 가이드를 읽어야합니다.

나는이 스타일을 매우 일관되게 사용하고 모든 함수에는 int retval, out_freelabel 및 out 레이블 이 있다고 덧붙여 야합니다 . 스타일 일관성으로 인해 가독성이 향상되었습니다.

보너스 : 휴식과 계속

while 루프가 있다고 가정 해보십시오.

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);

    if( functionC(var1, var2){
         count++
         continue;
    }

    ...
    a bunch of statements
    ...

    count++;
    free(var1);
    free(var2);
}

이 코드에는 다른 문제가 있지만, 한 가지는 continue 문입니다. 전체 내용을 다시 작성하고 싶지만 작은 방식으로 수정해야했습니다. 나를 만족시키는 방식으로 리팩토링하는 데 며칠이 걸렸지 만 실제 변화는 약 반일의 작업이었습니다. 문제는 우리가 '경우에도 것입니다 continue우리가 아직 확보해야' var1하고 var2. 나는을 추가 var3해야했고, free () 문을 반영하기 위해 노력하고 싶었다.

나는 당시에 비교적 새로운 인턴이지만, 잠시 동안 리눅스 소스 코드를보고 있었기 때문에 관리자에게 goto 문을 사용할 수 있는지 물었다. 그는 그렇다고 말했습니다.

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);
    var3 = newFunction(line,count);

    if( functionC(var1, var2){
         goto next;
    }

    ...
    a bunch of statements
    ...
next:
    count++;
    free(var1);
    free(var2);
}

나는 계속되는 것이 최선이라고 생각하지만 나에게는 그들은 보이지 않는 레이블이있는 goto와 같습니다. 휴식도 마찬가지입니다. 여기서와 마찬가지로 여러 위치에서 수정 사항을 미러링하도록 강요하지 않는 한 계속 또는 중단을 선호합니다.

또한이 사용 goto next;next:레이블이 만족스럽지 않다고 덧붙여 야합니다 . free()count++진술 과 진술을 반영하는 것보다 낫습니다 .

goto의 경우는 거의 항상 잘못된 것이지만 사용하기 좋은시기를 알아야합니다.

내가 논의하지 않은 한 가지는 다른 답변에 의해 다루어 진 오류 처리입니다.

공연

strtok ()의 ​​구현을 볼 수 있습니다 http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c

#include <stddef.h>
#include <string.h>

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}

내가 틀렸다면 정정하십시오. 그러나 cont:레이블과 goto cont;문장이 성능을 위해 있다고 생각합니다 (확실히 코드를 읽을 수있게하지는 않습니다). 이렇게하면 읽을 수있는 코드로 대체 될 수 있습니다

while( isDelim(*s++,delim));

구분자를 건너 뛰려면 그러나 가능한 빨리하고 불필요한 함수 호출을 피하기 위해이 방법을 사용합니다.

나는 Dijkstra의 논문을 읽었고 나는 매우 난해한 것을 발견했다.

Google은 2 개 이상의 링크를 게시 할만큼 평판이 충분하지 않기 때문에 "dijkstra goto 문이 유해한 것으로 간주됩니다".

나는 그것이 goto를 사용하지 않는 이유라고 인용 한 것을 보았고 그것을 읽는 것이 나의 goto를 사용하는 한 아무것도 변하지 않았다.

부록 :

나는이 모든 것에 대해 계속 생각하고 깨는 동안 깔끔한 규칙을 생각해 냈습니다.

  • while 루프에 continue가 있으면 while 루프의 본문은 함수 여야하며 continue는 return 문입니다.
  • while 루프에 break 문이 있으면 while 루프 자체가 함수 여야하며 break가 return 문이되어야합니다.
  • 둘 다 가지고 있다면 무언가 잘못되었을 수 있습니다.

범위 문제로 인해 항상 가능한 것은 아니지만이 작업을 수행하면 코드에 대해 추론하기가 훨씬 쉽다는 것을 알았습니다. 나는 while 루프가 끊어 지거나 계속 될 때마다 기분이 나쁘다는 것을 알았습니다.


2
+1이지만 한 지점에서 동의하지 않을 수 있습니까? "고토가 악하다고 믿으면 프로그래머를 키울 필요가 있다고 생각한다." 어쩌면 1975 년에 텍스트 편집기없이 줄 번호와 GOTO를 사용하여 BASIC에서 프로그래밍하는 법을 처음 배웠습니다. 10 년 후 구조적 프로그래밍을 만났습니다. 멈출 압력. 오늘날, 나는 여러 가지 이유로 일년에 몇 번씩 GOTO를 사용하지만 많이 나오지 않습니다. GOTO가 악하다고 믿지 않아서 내가 아는 해를 끼치 지 않았으며 심지어 선을 행했을 수도 있습니다. 그저 나야
thb

1
나는 당신이 그것에 대해 맞다고 생각합니다. 나는 GOTO가 사용되지 않을 것이라는 생각을 가지고 있었고 순전 한 상황에 따라 메모리가있는 여러 종료 지점이있는이 기능을 가진 코드를 무료로 사용할 때 Linux 소스 코드를 탐색하고있었습니다. 그렇지 않으면 이러한 기술에 대해 전혀 알지 못했을 것입니다.
Philippe Carphin

1
@thb 또한 재미있는 이야기로, 당시 GOTO의 사용 허가를 인턴으로 감독관에게 물었고 나는 그에게 사용 된 방식과 같은 특정 방식으로 사용할 것이라고 설명했습니다. 리눅스 커널과 그는 말했다.
Philippe Carphin

1
@thb 나는 이것 처럼 (루프를 끊는 대신) 루프로 들어가는 것이 좋은지 모른다 . 글쎄, 그것은 나쁜 예이지만, Knuth의 구조적 프로그래밍 에 대한 goto 문 (예 7a)이있는 quicksort 는 go to Statements로 이해 하기 쉽지 않습니다.
Yai0Phah

@ Yai0Phah 나는 나의 요점을 설명 할 것이지만 나의 설명은 당신의 훌륭한 예 7a를 감소시키지 않습니다! 예제를 승인합니다. 그래도 우호적 인 2 학년은 사람들에게 고토에 대해 강의하는 것을 좋아합니다. 1985 년 이래로 심각한 문제를 일으키는 goto를 실제로 사용하기는 어렵지만 프로그래머의 작업을 용이하게하는 무해한 고토를 찾을 수 있습니다. 어쨌든 현대 프로그래밍에서 거의 발생하지 않습니다. 어떻게하면 내 조언은, 그것을 사용하고 싶다면 아마도 그것을 사용해야한다는 것입니다. 고토는 괜찮습니다. goto의 주요 문제점은 일부 사람들은 goto 를 사용하지 않으면 스마트하게 보이게 된다는 것입니다.
THB

5

개인적으로 나는 이것을 다음과 같이 더 리팩토링 할 것입니다 :

int DoLotsOfStuffThatCouldFail (paramstruct *params)
{
    int errcode = EC_NOERROR;

    if ((errcode = FunctionThatCouldFail1 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail2 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail3 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail4 (params)) != EC_NOERROR) return errcode;

    return EC_NOERROR;
}

void DoStuff (paramstruct *params)
{
    int errcode = EC_NOERROR;

    InitStuffThatMayNeedToBeCleaned (params);

    if ((errcode = DoLotsOfStuffThatCouldFail (params)) != EC_NOERROR)
    {
         CleanupAfterError (params, errcode);
    }
}

그러나 goto를 피하는 것보다 깊은 중첩을 피함으로써 더 많은 동기를 부여 할 수 있습니다 (IMO는 첫 번째 코드 샘플에서 더 나쁜 문제임) 물론 범위 밖에서 가능한 CleanupAfterError (이 경우 "params" 비워야 할 할당 된 메모리, 닫는 데 필요한 FILE * 등이 포함 된 구조체 여야합니다).

이 접근 방식에서 볼 수있는 주요 이점 중 하나는 FTCF2와 FTCF3 사이에 가상의 미래 추가 단계를 배치하는 것이 더 쉽고 깨끗하다는 것입니다 (따라서 기존의 현재 단계 제거). 내 코드를 상속받지 않으려 고합니다!)-옆으로 가면 중첩 된 버전에는 부족합니다.


1
나는 내 질문에 이것을 언급하지 않았지만, FTCF가 동일한 매개 변수를 가지고 있지 않아서이 패턴을 조금 더 복잡하게 만들 수 있습니다. 그래도 고마워.
Robz

3

엄격한 기준에 따라 진행할 수있는 MISRA (Motor Industry Software Reliability Association) C 코딩 지침을 살펴보십시오 (예제가 충족되는 경우).

내가 작업 할 때 동일한 코드가 작성되는 곳-고토가 필요 없음-불필요하게 종교적인 논쟁을 피하는 것은 모든 소프트웨어 하우스에서 큰 장점입니다.

error = function_that_could_fail_1();
if(!error) {
  error = function_that_could_fail_2();
}
if(!error) {
  error = function_that_could_fail_3();
} 
if(!error) {
...
if (error) {
  cleanup:
} 

또는 "goto in drag"-goto보다 더 피할 것이지만 "No goto Ever !!!" camp) "확실히 그래야합니다. Goto를 사용하지 마십시오"....

do {
  if (error = function_that_could_fail_1() ){
    break 
  }
  if (error = function_that_could_fail_2() ){
    break 
  }
  ....... 
} while (0) 
cleanup();
.... 

함수가 동일한 매개 변수 유형을 갖는 경우 테이블에 넣고 루프를 사용하십시오.


2
현행 MISRA-C : 2004 지침은 어떠한 형태의 이동도 허용 하지 않습니다 (규칙 14.4 참조). MISRA위원회는 항상 이것에 대해 혼동되어 왔으며, 어떤 발을 밟을지는 모릅니다. 첫째, 그들은 무조건 goto의 사용을 금지하고 계속합니다. 그러나 다가오는 MISRA 2011 초안에 그들은 다시 허용하고 싶습니다. 참고로, MISRA는 if 문 내부 할당을 금지합니다. 왜냐하면 goto를 사용하는 것보다 훨씬 위험하기 때문입니다.

1
분석적 관점에서 프로그램에 플래그를 추가하는 것은 플래그가 범위 내에있는 모든 코드 코드를 복제하는 것과 동일합니다 if(flag). 한 사본의 모든 "if"분기를 사용하고 다른 사본의 각 해당 명령문을 " 그밖에". 플래그를 설정하고 지우는 작업은 실제로 두 버전의 코드 사이를 이동하는 "고 토스"입니다. 플래그를 사용하는 것이 다른 방법보다 깨끗할 때가 있지만, 하나의 goto목표 를 저장하기 위해 플래그를 추가하는 것은 좋은 트레이드 오프가 아닙니다.
supercat

1

goto대체 do/while/continue/break해커가 읽기 어려운 경우 에도 사용 합니다 .

goto그들의 목표는 이름이 있고 읽는다는 이점이 goto something;있습니다. 실제로 읽기 break또는 continue중지하지 않은 경우 보다 읽기 쉽습니다.


4
어디서든 내부의 do ... while(0)실제 루프 만의 사용을 방지하기 위해 무모한 시도가 아니거나 다른 구조 goto.
aib

1
아, 고마워, 나는 "왜 누군가가 그렇게 할까?"라는이 특별한 브랜드를 몰랐다. 아직 구조.
Benjamin Kloster

2
일반적으로 해커는 do / while / continue / break 해커가 포함되어있는 모듈이 처음부터 너무 오래 갈 때만 읽을 수 없게됩니다.
John R. Strohm

2
goto를 사용하는 것이 정당하다고 여기에는 아무것도 없습니다. 끊고 계속하는 것은 분명한 결과입니다. 고토 ... 어디? 라벨은 어디에 있습니까? 다음 단계가 어디 있는지와 그 근처를 정확히 알려주십시오.
Rig

1
물론 루프 내에서 레이블을 볼 수 있어야합니다. @John R. Strohm의 의견 중 일부에 동의합니다. 그리고 루프 해커로 번역 된 당신의 요점은 "무엇을 깨뜨릴까요? 이것은 루프가 아닙니다!"가됩니다. 어쨌든 이것은 OP가 두려워 할 수있는 것이되고 있으므로 토론을 포기하고 있습니다.
aib

-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:

루프가 하나만 있으면 낙인이 없지만 break정확히 작동합니다 goto.
9000

6
-1 : 먼저, x와 y가 발견되지 않았습니다. : 이것은 도움이되지 않습니다. 둘째, 코드가 작성된 상태에서 당신이 도착했다는 사실이 당신이 찾고있는 것을 찾았다는 것을 의미하지는 않습니다.
John R. Strohm

이것은 여러 개의 루프에서 벗어날 때 생각할 수있는 가장 작은 예이기 때문입니다. 더 나은 라벨 또는 완료된 점검을 위해 자유롭게 편집하십시오.
aib

1
그러나 C 함수가 반드시 부작용이있는 것은 아닙니다.
aib

1
@ JohnR.Strohm 많은 의미가 없습니다 ... 'found'라벨은 변수를 확인하지 않고 루프를 끊는 데 사용됩니다. 변수를 확인하려면 다음과 같이 할 수 있습니다 : for (int y = 0; y <height; ++ y) {for (int x = 0; x <width; ++ x) {if (find ( x, y)) {doSomeThingWith (x, y); 고토 발견; }}} 발견 :
YoYoYonnY

-1

한 가지 방법은 허용되고 다른 방법은 허용되지 않는 캠프가 항상 있습니다. 내가 일한 회사는 goto 사용을 찌푸 리거나 낙담했습니다. 개인적으로, 나는 한 번 사용한 시간을 생각할 수 없지만 그것이 나쁜 일이 아니라 다른 일을하는 방법을 의미하지는 않습니다 .

C에서는 일반적으로 다음을 수행합니다.

  • 처리를 방해 할 수있는 조건 (잘못된 입력 등) 및 "반환"을 테스트
  • 리소스 할당이 필요한 모든 단계 (예 : malloc)를 수행하십시오.
  • 여러 단계에서 성공 여부를 확인하는 처리 수행
  • 성공적으로 할당되면 모든 자원을 해제하십시오
  • 결과를 반환

처리를 위해 goto 예제를 사용하여 다음과 같이하십시오.

오류 = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }

중첩이없고 if 절 내부에서 단계에서 오류가 발생하면 오류보고를 수행 할 수 있습니다. 따라서 gotos를 사용하는 방법보다 "걱정"할 필요는 없습니다.

다른 방법으로는 할 수없고 읽을 수 있고 이해할 수 있고 IMHO의 핵심 인 누군가가해야 할 경우를 아직 거치지 않았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.