루프와 함수를 지원하는 언어에서 'goto'를 사용하는 것이 유리한가요?그렇다면 왜 그렇습니까?

StackOverflow https://stackoverflow.com/questions/24451

문제

나는 오랫동안 그런 인상을 받아왔다. goto 가능하면 절대 사용하면 안 됩니다.얼마 전 C로 작성된 libavcodec을 자세히 살펴보던 중 나는 이 코덱이 여러 번 사용되는 것을 발견했습니다.사용하는 것이 유리합니까? goto 루프와 기능을 지원하는 언어로?그렇다면 왜 그렇습니까?

도움이 되었습니까?

해결책

내가 알고 있는 것처럼 "goto" 문을 사용하는 데는 몇 가지 이유가 있습니다(일부는 이미 이에 대해 설명했습니다).

함수를 깔끔하게 종료하기

함수에서 리소스를 할당하고 여러 위치에서 종료해야 하는 경우가 종종 있습니다.프로그래머는 리소스 정리 코드를 함수 끝에 배치하여 코드를 단순화할 수 있으며 함수의 모든 "종료 지점"은 정리 레이블로 이동합니다.이렇게 하면 함수의 모든 "종료 지점"에 정리 코드를 작성할 필요가 없습니다.

중첩 루프 종료

중첩된 루프에 있고 이를 벗어나야 하는 경우 모두 루프에서 goto는 break 문이나 if-check보다 훨씬 깔끔하고 간단하게 만들 수 있습니다.

낮은 수준의 성능 개선

이는 성능이 중요한 코드에서만 유효하지만 goto 문은 매우 빠르게 실행되며 함수를 통해 이동할 때 속도를 높일 수 있습니다.그러나 이는 양날의 검입니다. 왜냐하면 컴파일러는 일반적으로 gotos가 포함된 코드를 최적화할 수 없기 때문입니다.

이 모든 예에서 gotos는 단일 함수의 범위로 제한됩니다.

다른 팁

안티이신 분들은 모두goto Edsger Dijkstra의 글을 직간접적으로 인용합니다. GoTo는 유해한 것으로 간주됨 자신의 입장을 입증하기 위한 기사입니다.안타깝게도 Dijkstra의 기사는 사실상 아무것도 아님 길과 관련이 있다 goto 요즘에는 명령문이 사용되므로 기사에서 말하는 내용은 현대 프로그래밍 장면에 적용할 수 있는 내용이 거의 또는 전혀 없습니다.그만큼 goto-적은 밈은 이제 종교, 즉 높은 곳에서 지시된 경전, 대제사장, 이단자로 인식되는 사람을 피하는(또는 더 나쁜) 것에 이르기까지 거의 종교에 가깝습니다.

주제에 대해 약간의 조명을 제공하기 위해 Dijkstra의 논문을 맥락에 맞게 살펴보겠습니다.

Dijkstra가 논문을 썼을 때 당시 인기 있는 언어는 BASIC, FORTRAN(이전 방언) 및 다양한 어셈블리 언어와 같은 구조화되지 않은 절차적 언어였습니다.상위 언어를 사용하는 사람들이 점프하는 것은 꽤 흔한 일이었습니다. 코드 베이스 전반에 걸쳐 뒤틀리고 뒤틀린 실행 스레드로 인해 "스파게티 코드"라는 용어가 탄생했습니다.이 내용은 다음 페이지로 이동하여 확인할 수 있습니다. 클래식 트렉 게임 Mike Mayfield가 작성했으며 상황이 어떻게 작동하는지 알아 내려고 노력했습니다.잠시 시간을 내어 자세히 살펴보세요.

이것 Dijkstra가 1968년 자신의 논문에서 비난한 "go to 문을 무제한적으로 사용하는 것"입니다. 이것 그가 그 논문을 쓰게 된 것은 그가 살았던 환경 때문입니다.코드에서 원하는 지점이나 원하는 지점으로 이동할 수 있는 능력은 그가 비판하고 중단을 요구했던 것이었습니다.빈혈의 힘과 비교하면 goto C나 다른 현대 언어에서는 단순히 위험합니다.

나는 이미 이단자들에 맞서는 광신도들의 함성 소리를 들을 수 있습니다."하지만" 그들은 "코드를 읽기 어렵게 만들 수 있다"고 외칠 것입니다. goto C." 아 그래요?없이는 코드를 읽기 어렵게 만들 수 있습니다. goto 또한.이 같은:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

아님 goto 눈에 보이니까 읽기 쉬울 텐데요, 그렇죠?아니면 이건 어떻습니까?

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

아니요 goto 거기도요.그러므로 읽을 수 있어야 합니다.

이 예에서 내 요점은 무엇입니까?읽을 수 없고 유지 관리할 수 없는 코드를 만드는 것은 언어 기능이 아닙니다.이를 수행하는 것은 구문이 아닙니다.이 문제를 일으키는 것은 나쁜 프로그래머입니다.그리고 위의 항목에서 볼 수 있듯이 나쁜 프로그래머는 어느 언어 기능을 읽을 수 없고 사용할 수 없습니다.처럼 for 거기에 루프가 있습니다.(보실 수 있겠죠?)

공정하게 말하면 일부 언어 구조는 다른 언어 구조보다 남용하기가 더 쉽습니다.그러나 당신이 C 프로그래머라면 나는 C 프로그래머의 사용 중 약 50%를 훨씬 더 자세히 살펴볼 것입니다. #define 내가 적군에 맞서기 훨씬 전에 goto!

따라서 여기까지 읽으시느라 고생하신 분들을 위해 주목해야 할 몇 가지 핵심 사항이 있습니다.

  1. Dijkstra의 논문 goto 명령문은 프로그래밍 환경을 위해 작성되었습니다. goto 했다 많은어셈블러가 아닌 대부분의 현대 언어보다 잠재적으로 더 해로울 수 있습니다.
  2. 모든 용도를 자동으로 삭제 goto 이 때문에 "나는 한 번 재미를 시도했지만 지금은 그것을 좋아하지 않았기 때문에 지금은 반대한다"고 말하는 것만 큼 합리적입니다.
  3. 현대(빈혈)의 합법적인 사용이 있습니다. goto 다른 구성으로 적절하게 대체 할 수없는 코드의 진술.
  4. 물론 동일한 진술을 불법적으로 사용하는 경우도 있습니다.
  5. "와 같은 현대 제어문을 불법적으로 사용하는 경우도 있습니다.godo"항상 거짓인 혐오스러운 것 do 루프가 사용 중 중단되었습니다. break 대신에 goto.이는 종종 현명한 사용보다 더 나쁩니다. goto.

모범 사례를 맹목적으로 따르는 것은 모범 사례가 아닙니다.피한다는 생각 goto 명령문을 흐름 제어의 기본 형태로 사용하는 것은 읽을 수 없는 스파게티 코드가 생성되는 것을 방지하는 것입니다.적절한 위치에 아껴서 사용한다면 때로는 아이디어를 표현하는 가장 단순하고 명확한 방법이 될 수 있습니다.Zortech C++ 컴파일러와 D 프로그래밍 언어의 창시자인 Walter Bright는 이를 자주 사용하지만 신중하게 사용합니다.심지어 goto 그의 코드는 여전히 완벽하게 읽을 수 있습니다.

요점:회피 goto 피하기 위해서 goto 무의미하다.정말로 피하고 싶은 것은 읽을 수 없는 코드를 생성하는 것입니다.만약 당신의 goto-라덴 코드를 읽을 수 있으면 아무런 문제가 없습니다.

부터 goto 프로그램 흐름에 대한 추론을 어렵게 만듭니다.1 (일명."스파게티 코드"), goto 일반적으로 누락된 기능을 보완하는 데에만 사용됩니다.사용 goto 실제로는 허용될 수 있지만 언어가 동일한 목표를 달성하기 위해 더 구조화된 변형을 제공하지 않는 경우에만 가능합니다.의심의 예를 들어보자:

우리가 사용하는 goto의 규칙은 함수의 단일 종료 정리 지점으로 앞으로 점프하는 데 goto가 괜찮다는 것입니다.

이는 사실입니다. 하지만 언어가 정리 코드(예: RAII 또는 finally), 동일한 작업을 더 잘 수행하는 경우(이 작업을 수행하기 위해 특별히 제작되었기 때문에) 또는 구조적 예외 처리를 사용하지 않는 타당한 이유가 있는 경우(그러나 매우 낮은 수준을 제외하고는 이 경우가 발생하지 않습니다).

대부분의 다른 언어에서는 goto 중첩 루프를 종료하는 것입니다.그리고 거기에서도 외부 루프를 자체 방법으로 들어 올려 사용하는 것이 거의 항상 더 좋습니다. return 대신에.

그 이외의, goto 이는 특정 코드 부분에 충분한 생각이 들어가지 않았다는 신호입니다.


1 지원하는 현대 언어 goto 몇 가지 제한 사항을 구현합니다(예: goto 기능에 들어가거나 빠져나오지 못할 수도 있음) 그러나 문제는 근본적으로 동일합니다.

덧붙여서, 다른 언어 기능, 특히 예외적인 경우에도 마찬가지입니다.그리고 일반적으로 예외가 아닌 프로그램 흐름을 제어하기 위해 예외를 사용하지 않는 규칙과 같이 표시된 경우에만 이러한 기능을 사용하는 엄격한 규칙이 있습니다.

글쎄, 항상 그것보다 더 나쁜 것이 하나 있어요 goto's;goto를 피하기 위해 다른 프로그램 흐름 연산자를 이상하게 사용:

예:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

~ 안에 씨# 스위치 성명 추락을 허용하지 않는다.그래서 이동 특정 스위치 케이스 레이블 또는 기본 상표.

예를 들어:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

편집하다:"낙하 방지" 규칙에는 한 가지 예외가 있습니다.Case 문에 코드가 없으면 Fall-through가 허용됩니다.

#ifdef TONGUE_IN_CHEEK

펄에는 goto 이를 통해 가난한 사람의 꼬리 호출을 구현할 수 있습니다.:-피

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

좋아요, 그럼 C와는 아무 상관이 없군요 goto.더 진지하게, 나는 사용에 관한 다른 의견에 동의합니다. goto 정리 또는 구현을 위해 더프의 장치, 또는 이와 유사한 것.남용하는 것이 아니라 사용하는 것이 중요합니다.

(같은 의견이 다음에도 적용될 수 있습니다. longjmp, 예외, call/cc, 등—합법적인 용도로 사용되지만 쉽게 남용될 수 있습니다.예를 들어, 완전히 예외가 아닌 상황에서 깊게 중첩된 제어 구조를 탈출하기 위해 예외를 던집니다.)

나는 수년에 걸쳐 몇 줄 이상의 어셈블리 언어를 작성했습니다.궁극적으로 모든 고급 언어는 gotos로 컴파일됩니다.좋습니다. "가지"나 "점프" 또는 다른 이름으로 부르세요. 하지만 그것들은 고토스입니다.누구나 goto-less 어셈블러를 작성할 수 있나요?

물론 Fortran, C 또는 BASIC 프로그래머에게 gotos를 사용하여 폭동을 일으키는 것은 스파게티 볼로네즈를 만드는 방법이라고 지적할 수 있습니다.그러나 대답은 이를 피하는 것이 아니라 신중하게 사용하는 것입니다.

칼은 음식을 준비하거나, 누군가를 구출하거나, 누군가를 죽이는 데 사용될 수 있습니다.후자에 대한 두려움 때문에 우리는 칼 없이 지내는가?마찬가지로 goto:부주의하게 사용하면 방해가 되고 조심스럽게 사용하면 도움이 됩니다.

보세요 C로 프로그래밍할 때 Goto를 사용하는 경우:

goto를 사용하는 것은 거의 항상 나쁜 프로그래밍 습관이지만(확실히 XYZ를 수행하는 더 나은 방법을 찾을 수 있습니다), 실제로는 나쁜 선택이 아닌 경우가 있습니다.어떤 사람들은 그것이 유용할 때 그것이 최선의 선택이라고 주장할 수도 있습니다.

내가 goto에 관해 말해야 하는 대부분의 내용은 실제로 C에만 적용됩니다.C++를 사용하는 경우 예외 대신 goto를 사용할 이유가 없습니다.그러나 C에는 예외 처리 메커니즘이 없으므로 나머지 프로그램 논리에서 오류 처리를 분리하고 코드 전체에서 정리 코드를 여러 번 다시 작성하지 않으려면 다음을 수행하십시오. 그렇다면 goto가 좋은 선택이 될 수 있습니다.

내 말은 무슨 뜻인가요?다음과 같은 코드가 있을 수 있습니다.

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

정리 코드를 변경해야 한다는 사실을 깨닫기 전까지는 괜찮습니다.그런 다음 4가지 사항을 변경해야 합니다.이제 모든 정리 작업을 단일 함수로 캡슐화할 수 있다고 결정할 수 있습니다.그것은 나쁜 생각이 아닙니다.그러나 이는 포인터에 주의해야 함을 의미합니다. 정리 함수에서 포인터를 해제하려는 경우 포인터에 대한 포인터를 전달하지 않는 한 포인터를 NULL을 가리키도록 설정할 방법이 없습니다.많은 경우 어쨌든 해당 포인터를 다시 사용하지 않을 것이므로 이는 큰 문제가 아닐 수 있습니다.반면에 새 포인터, 파일 핸들 또는 정리가 필요한 기타 항목을 추가하는 경우 정리 기능을 다시 변경해야 합니다.그런 다음 해당 함수에 대한 인수를 변경해야 합니다.

사용하여 goto, 그럴 것이다

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

여기서 이점은 코드 다음의 정리를 수행하는 데 필요한 모든 것에 액세스할 수 있고 변경 지점 수를 상당히 줄일 수 있다는 것입니다.또 다른 이점은 함수에 대한 여러 종료 지점이 단 하나로 바뀌었다는 것입니다.정리하지 않고 실수로 함수에서 돌아올 가능성은 없습니다.

더욱이 goto는 단일 지점으로 이동하는 데만 사용되기 때문에 함수 호출을 시뮬레이션하기 위해 앞뒤로 점프하는 대량의 스파게티 코드를 생성하는 것과는 다릅니다.오히려 goto는 실제로 더 구조화된 코드를 작성하는 데 도움이 됩니다.


한마디로, goto 항상 최후의 수단으로 사용해야 하지만 적절한 시기와 장소가 있습니다.질문은 "꼭 사용해야 하는가"가 아니라, "그것이 최선의 선택인가"라는 질문이 되어야 합니다.

goto가 나쁜 이유 중 하나는 코딩 스타일 외에도 Goto를 사용하여 겹치는, 하지만 중첩되지 않은 루프:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

이는 (a, b, c, b, a, b, a, b, ...)와 같은 시퀀스가 ​​가능한 기괴하지만 합법적인 제어 흐름 구조를 생성하여 컴파일러 해커를 불행하게 만듭니다.분명히 이러한 유형의 구조가 발생하지 않도록 하는 영리한 최적화 트릭이 많이 있습니다.(내 드래곤 책 사본을 확인해야 합니다...) 결과적으로 (일부 컴파일러를 사용하여) 다음을 포함하는 코드에 대해 다른 최적화가 수행되지 않을 수 있습니다. goto에스.

다음과 같은 경우 유용할 수 있습니다. 알다 "아, 그런데" 컴파일러가 더 빠른 코드를 생성하도록 설득하는 것뿐입니다.개인적으로 나는 goto와 같은 트릭을 사용하기 전에 가능한 것과 그렇지 않은 것에 대해 컴파일러에게 설명하는 것을 선호하지만 틀림없이 시도해 볼 수도 있습니다. goto 어셈블러를 해킹하기 전에.

나는 어떤 사람들이 goto가 허용되는 경우 목록을 제공하고 다른 모든 용도는 허용되지 않는다고 말하는 것이 재미 있다고 생각합니다.goto가 알고리즘을 표현하기 위한 최선의 선택인 모든 경우를 정말로 알고 있다고 생각하십니까?

설명을 위해 여기 아무도 아직 보여주지 않은 예를 들어 보겠습니다.

오늘 저는 해시 테이블에 요소를 삽입하는 코드를 작성하고 있었습니다.해시 테이블은 마음대로 덮어쓸 수 있는 이전 계산의 캐시입니다(성능에 영향을 주지만 정확성에는 영향을 미치지 않음).

해시 테이블의 각 버킷에는 4개의 슬롯이 있으며 버킷이 가득 찼을 때 덮어쓸 요소를 결정하는 여러 기준이 있습니다.현재 이는 다음과 같이 버킷을 통해 최대 3개의 패스를 만드는 것을 의미합니다.

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

이제 goto를 사용하지 않았다면 이 코드는 어떤 모습일까요?

이 같은:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

더 많은 패스가 추가되면 상황은 점점 더 나빠질 것입니다. 반면 goto가 있는 버전은 항상 동일한 들여쓰기 수준을 유지하고 이전 루프의 실행으로 결과가 암시되는 가짜 if 문의 사용을 피합니다.

goto를 사용하면 코드가 더욱 깔끔하고 작성 및 이해가 쉬워지는 또 다른 사례가 있습니다.더 많은 경우가 있을 거라 확신합니다. 따라서 goto가 유용한 경우를 모두 아는 척하지 말고, 생각하지 못한 좋은 경우를 디스하지 마세요.

우리가 사용하는 goto의 규칙은 함수의 단일 종료 정리 지점으로 앞으로 점프하는 데 goto가 괜찮다는 것입니다.매우 복잡한 기능에서는 다른 점프 포워드를 허용하기 위해 해당 규칙을 완화합니다.두 경우 모두 오류 코드 검사에서 자주 발생하는 깊게 중첩된 if 문을 방지하여 가독성과 유지 관리에 도움이 됩니다.

goto 문, 합법적인 용도, "유능한 goto 문" 대신 사용할 수 있지만 goto 문만큼 쉽게 남용될 수 있는 대체 구성에 대한 가장 사려 깊고 철저한 논의는 Donald Knuth의 기사입니다.goto 문을 사용한 구조적 프로그래밍", 1974년 12월 Computing Surveys(6권, no.4.pp.261~301).

놀랍지 않게도 이 39년 된 논문의 일부 측면에는 날짜가 기록되어 있습니다.처리 능력이 엄청나게 증가하면 Knuth의 성능 향상 중 일부는 중간 크기의 문제에서는 눈에 띄지 않게 되며 그 이후로 새로운 프로그래밍 언어 구성이 발명되었습니다.(예를 들어 try-catch 블록은 Zahn's Construct를 포함하지만 그런 식으로 거의 사용되지는 않습니다.) 그러나 Knuth는 논쟁의 모든 측면을 다루며 누군가가 문제를 다시 해시하기 전에 읽어야 합니다.

Perl 모듈에서는 때때로 서브루틴이나 클로저를 즉석에서 만들고 싶을 때가 있습니다.문제는 서브루틴을 만든 후에 어떻게 해당 서브루틴에 접근하느냐 하는 것입니다.그냥 호출할 수도 있지만 서브루틴이 caller() 그다지 도움이 되지는 않을 것입니다.바로 그곳이 goto &subroutine 변형이 도움이 될 수 있습니다.

다음은 간단한 예입니다.

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

이 형식을 사용할 수도 있습니다. goto tail-call 최적화의 기초적인 형태를 제공합니다.

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

( 안에 펄 5 버전 16 그것은 다음과 같이 쓰는 것이 더 나을 것입니다 goto __SUB__; )

가져오는 모듈이 있습니다. tail 수정자와 가져올 수정자 recur 이 형식을 사용하는 것을 좋아하지 않는다면 goto.

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

사용하는 다른 대부분의 이유 goto 다른 키워드를 사용하는 것이 더 좋습니다.

좋다 redo약간의 코드를 작성합니다.

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

아니면 last 여러 곳에서 가져온 약간의 코드:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

그렇다면 왜 그렇습니까?

C에는 다중 레벨/레이블이 지정된 중단이 없으며 모든 제어 흐름이 C의 반복 및 결정 기본 요소로 쉽게 모델링될 수 있는 것은 아닙니다.gotos는 이러한 결함을 해결하기 위해 먼 길을 가고 있습니다.

때로는 일종의 의사 다중 레벨 중단에 영향을 미치기 위해 일종의 플래그 변수를 사용하는 것이 더 명확하지만 항상 goto보다 우수한 것은 아닙니다. 적어도 goto를 사용하면 플래그 변수와 달리 제어가 어디로 가는지 쉽게 결정할 수 있습니다. ), 때로는 goto를 피하기 위해 플래그/기타 왜곡의 성능 비용을 지불하고 싶지 않은 경우도 있습니다.

libavcodec은 성능에 민감한 코드입니다.제어 흐름을 직접적으로 표현하는 것이 더 잘 실행되는 경향이 있기 때문에 아마도 우선순위일 것입니다.

마찬가지로 아무도 "COME FROM" 문을 구현하지 않았습니다....

나는 do{} while(false) 사용법이 완전히 역겹다고 생각합니다.어떤 이상한 경우에는 이것이 필요하다는 것을 나에게 확신시킬 수도 있지만 그것이 깨끗하고 합리적인 코드라고는 결코 확신할 수 없습니다.

그러한 루프를 수행해야 한다면 플래그 변수에 대한 종속성을 명시적으로 만드는 것은 어떨까요?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

물론 GOTO를 사용할 수도 있지만 코드 스타일보다 중요한 것이 하나 더 있습니다. 또는 코드를 읽을 수 있는지 여부를 사용할 때 염두에 두어야 할 점은 다음과 같습니다. 내부 코드는 생각만큼 강력하지 않을 수 있습니다..

예를 들어 다음 두 코드 조각을 살펴보세요.

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

GOTO와 동등한 코드

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

우리가 가장 먼저 생각하는 것은 두 코드 비트의 결과가 "A의 값:0"(물론 병렬성이 없는 실행을 가정합니다)

그것은 정확하지 않습니다:첫 번째 샘플에서 A는 항상 0이지만 두 번째 샘플(GOTO 문 사용)에서는 A가 0이 아닐 수도 있습니다.왜?

그 이유는 프로그램의 다른 지점에서 GOTO FINAL A의 가치를 통제하지 않고.

이 예는 매우 당연하지만 프로그램이 복잡해질수록 그런 것들을 보기가 어려워집니다.

관련 자료는 이씨의 유명한 글에서 확인할 수 있다.데이크스트라 "GO TO 진술에 대한 사건"

1) 내가 아는 goto의 가장 일반적인 용도는 Goto를 제공하지 않는 언어, 즉 C에서 예외 처리를 에뮬레이트하는 것입니다.(위의 Nuclear에서 제공한 코드가 바로 그것입니다.) Linux 소스 코드를 보면 그런 식으로 사용된 수많은 코드를 볼 수 있습니다.2013년에 실시된 간단한 조사에 따르면 Linux 코드에는 약 100,000개의 gotos가 있었습니다. http://blog.regehr.org/archives/894.Goto 사용법은 Linux 코딩 스타일 가이드에도 언급되어 있습니다. https://www.kernel.org/doc/Documentation/CodingStyle.객체 지향 프로그래밍이 함수 포인터로 채워진 구조체를 사용하여 에뮬레이션되는 것처럼 goto는 C 프로그래밍에서 그 자리를 차지합니다.그렇다면 누가 옳은가?Dijkstra 또는 Linus(및 모든 Linux 커널 코더)?이론과 비교입니다.기본적으로 연습하세요.

그러나 컴파일러 수준 지원이 없고 일반적인 구성/패턴을 확인하지 못하는 일반적인 문제가 있습니다.컴파일 시간 확인 없이 잘못 사용하고 버그를 발생시키기가 더 쉽습니다.Windows 및 Visual C++이지만 C 모드에서는 바로 다음과 같은 이유로 SEH/VEH를 통한 예외 처리를 제공합니다.예외는 OOP 언어 외부에서도 유용합니다.절차적 언어로.그러나 컴파일러가 언어의 예외에 대한 구문 지원을 제공하더라도 항상 베이컨을 저장할 수는 없습니다.후자의 경우로 하나의 goto를 복제하여 비참한 결과를 초래하는 유명한 Apple SSL "goto failure" 버그를 예로 들어 보겠습니다.https://www.imperialviolet.org/2014/02/22/applebug.html):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

컴파일러 지원 예외를 사용하면 정확히 동일한 버그가 발생할 수 있습니다.C++에서:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

그러나 컴파일러가 도달할 수 없는 코드를 분석하고 경고하면 두 가지 버그 변형을 모두 피할 수 있습니다.예를 들어 /W4 경고 수준에서 Visual C++로 컴파일하면 두 경우 모두에서 버그가 발견됩니다.예를 들어 Java는 다음과 같은 이유로 도달할 수 없는 코드(찾을 수 있는 코드)를 금지합니다.이는 일반적인 Joe의 코드에 있는 버그일 가능성이 높습니다.goto 구문이 계산된 주소에 대한 goto(**)와 같이 컴파일러가 쉽게 알아낼 수 없는 대상을 허용하지 않는 한, Dijkstra를 사용하는 것보다 컴파일러가 goto가 있는 함수 내에서 도달할 수 없는 코드를 찾는 것이 더 어렵지 않습니다. -승인된 코드.

(**) 각주:계산된 줄 번호로 이동하는 것은 일부 Basic 버전에서 가능합니다.GOTO 10*x 여기서 x는 변수입니다.오히려 혼란스럽게도 Fortran에서 "계산된 goto"는 C의 스위치 문과 동일한 구성을 나타냅니다.표준 C는 언어에서 계산된 goto를 허용하지 않지만 정적으로/구문적으로 선언된 레이블에 대한 goto만 허용합니다.그러나 GNU C에는 레이블 주소(단항, 접두사 && 연산자)를 가져오는 확장 기능이 있으며 void* 유형의 변수로 이동할 수도 있습니다.보다 https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html 이 모호한 하위 주제에 대해 자세히 알아보세요.이 게시물의 나머지 부분에서는 그 모호한 GNU C 기능에 대해서는 다루지 않습니다.

표준 C(예:계산되지 않음) gotos는 일반적으로 컴파일 타임에 도달할 수 없는 코드를 찾을 수 없는 이유가 아닙니다.일반적인 이유는 다음과 같은 논리 코드 때문입니다.주어진

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

컴파일러가 다음 3가지 구성에서 도달할 수 없는 코드를 찾는 것도 마찬가지로 어렵습니다.

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

(중괄호 관련 코딩 스타일이 아쉽지만 예제를 최대한 간결하게 유지하려고 노력했습니다.)

Visual C++ /W4(/Ox 포함)는 이들 중 어느 것에서도 도달할 수 없는 코드를 찾는 데 실패하며 아마도 여러분도 알고 있듯이 도달할 수 없는 코드를 찾는 문제는 일반적으로 결정할 수 없습니다.(내 말을 믿지 못한다면: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf)

관련 문제로 C goto를 사용하여 함수 본문 내부에서만 예외를 에뮬레이트할 수 있습니다.표준 C 라이브러리는 로컬이 아닌 종료/예외를 에뮬레이션하기 위한 setjmp() 및 longjmp() 함수 쌍을 제공하지만 다른 언어가 제공하는 것과 비교할 때 몇 가지 심각한 단점이 있습니다.위키피디아 기사 http://en.wikipedia.org/wiki/Setjmp.h 이 후자의 문제를 상당히 잘 설명합니다.이 함수 쌍은 Windows(http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx), 하지만 SEH/VEH가 우수하기 때문에 그곳에서는 이를 사용하는 사람이 거의 없습니다.Unix에서도 setjmp와 longjmp는 거의 사용되지 않는 것 같습니다.

2) C에서 goto의 두 번째로 가장 일반적인 사용은 다중 레벨 중단 또는 다중 레벨 계속을 구현하는 것이라고 생각합니다. 이는 또한 상당히 논란의 여지가 없는 사용 사례입니다.Java는 goto 레이블을 허용하지 않지만 break 레이블 또는 continue 레이블을 허용한다는 점을 기억하십시오.에 따르면 http://www.oracle.com/technetwork/java/simple-142616.html, 이것은 실제로 C에서 gotos의 가장 일반적인 사용 사례입니다(그들은 90%라고 말합니다). 그러나 주관적인 경험에 따르면 시스템 코드는 오류 처리를 위해 gotos를 더 자주 사용하는 경향이 있습니다.아마도 과학 코드나 OS가 예외 처리(Windows)를 제공하는 경우 다중 레벨 종료가 지배적인 사용 사례일 것입니다.그들은 설문조사의 맥락에 대해 어떤 세부 정보도 제공하지 않습니다.

다음을 추가하도록 편집되었습니다.이 두 가지 사용 패턴은 Kernighan과 Ritchie의 C 서적 60페이지(버전에 따라 다름)에서 찾을 수 있습니다.또 다른 주목할만한 점은 두 사용 사례 모두 전달 goto만 포함한다는 것입니다.그리고 MISRA C 2012 에디션(2004 에디션과 달리)에서는 이제 goto가 포워드(forward)인 경우에만 허용되는 것으로 나타났습니다.

Perl에서는 루프에서 "이동"하기 위해 레이블을 사용합니다. 이는 break와 유사한 "마지막" 문을 사용합니다.

이를 통해 중첩 루프를 더 효과적으로 제어할 수 있습니다.

전통적인 고토 상표 도 지원되지만 이것이 원하는 것을 달성하는 유일한 방법인 경우가 너무 많은지 잘 모르겠습니다. 대부분의 경우 서브루틴과 루프로 충분합니다.

'goto'의 문제점과 'goto-less 프로그래밍' 운동의 가장 중요한 주장은 이를 너무 자주 사용하면 코드가 올바르게 작동하더라도 읽을 수 없고 유지 관리할 수 없으며 검토할 수 없게 된다는 것입니다.99.99%의 경우 'goto'는 스파게티 코드로 이어집니다.개인적으로 나는 왜 'goto'를 사용해야 하는지에 대한 타당한 이유를 생각할 수 없습니다.

이 분야에서 큰 공헌을 한 컴퓨터 과학자 Edsger Dijkstra는 GoTo의 사용을 비판한 것으로도 유명했습니다.그의 주장에 대한 짧은 기사가 있습니다. 위키피디아.

다음과 같은 경우에는 goto를 사용합니다.다른 위치의 함수에서 반환해야 하는 경우, 반환하기 전에 일부 초기화를 수행해야 합니다.

Goto가 아닌 버전:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

버전으로 이동:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

두 번째 버전을 사용하면 할당 해제 문(각각은 코드에서 한 번 사용됨)에서 무언가를 변경해야 할 때 더 쉽게 할 수 있으며 새 분기를 추가할 때 해당 문을 건너뛸 가능성이 줄어듭니다.할당 해제가 다른 "수준"에서 수행될 수 있기 때문에 함수에서 이동하는 것은 여기서 도움이 되지 않습니다.

어떤 사람들은 C++에서는 goto를 사용할 이유가 없다고 말합니다.어떤 사람들은 99%의 경우 더 나은 대안이 있다고 말합니다. 이것은 추론이 아니라 단지 비합리적인 인상일 뿐입니다. 다음은 goto가 향상된 do-while 루프와 같은 멋진 코드로 이어지는 확실한 예입니다.

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

goto-free 코드와 비교해 보세요.

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

나는 다음과 같은 차이점을 봅니다.

  • 중첩된 {} 블록이 필요합니다(비록 do {...} while 좀 더 친숙해 보이네요)
  • 추가의 loop 변수가 필요하며 네 곳에서 사용됩니다.
  • 작품을 읽고 이해하는 데 시간이 더 오래 걸립니다. loop
  • 그만큼 loop 데이터를 보유하지 않고 실행 흐름을 제어할 뿐이므로 단순한 레이블보다 이해하기 어렵습니다.

또 다른 예가 있습니다

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

이제 "사악한" goto를 제거해 보겠습니다.

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

goto를 사용하는 것과 동일한 유형이라는 것을 알 수 있습니다. 구조가 잘 구성된 패턴이며 유일한 권장 방법만큼 많은 프로모션을 진행하는 것이 아닙니다.확실히 다음과 같은 "스마트" 코드는 피하고 싶을 것입니다.

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

요점은 goto가 쉽게 오용될 수 있지만 goto 자체에는 책임이 없다는 것입니다.label에는 C++의 함수 범위가 있으므로 순수 어셈블리처럼 전역 범위를 오염시키지 않습니다. 겹치는 루프 7세그먼트 디스플레이가 P1에 연결된 8051에 대한 다음 코드와 같이 그 자리가 있고 매우 일반적입니다.프로그램은 다음을 중심으로 번개 세그먼트를 반복합니다.

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

또 다른 장점이 있습니다:goto는 명명된 루프, 조건 및 기타 흐름의 역할을 할 수 있습니다.

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

또는 들여쓰기와 함께 동등한 goto를 사용할 수 있으므로 레이블 이름을 현명하게 선택하면 주석이 필요하지 않습니다.

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top