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


201

나는 goto가능하면 절대 사용해서는 안되는 인상을 오랫동안 받아왔다 . 다른 날 libavcodec (C로 작성)를 숙독하면서 여러 번 사용하는 것을 알았습니다. goto루프와 함수를 지원하는 언어 로 사용 하는 것이 유리한가요 ? 그렇다면 왜 그렇습니까?

답변:


242

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

기능을 완전히 종료

종종 함수에서 자원을 할당하고 여러 곳에서 종료해야 할 수도 있습니다. 프로그래머는 함수의 끝에 리소스 정리 코드를 넣어 코드를 단순화 할 수 있으며 함수의 모든 "종료 지점"이 정리 레이블로 이동합니다. 이런 식으로 함수의 "종료 지점"마다 정리 코드를 작성할 필요가 없습니다.

중첩 루프 종료

중첩 루프에 있고 모든 루프 를 해제해야하는 경우 goto는 break 문 및 if-check보다 훨씬 깨끗하고 간단합니다.

저수준 성능 개선

이것은 per-critical 코드에서만 유효하지만 goto 문은 매우 빠르게 실행되며 함수를 이동할 때 부스트를 줄 수 있습니다. 그러나 컴파일러는 일반적으로 gotos가 포함 된 코드를 최적화 할 수 없기 때문에 양날의 검입니다.

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


15
중첩 루프를 종료하는 올바른 방법은 내부 루프를 별도의 메소드로 리팩터링하는 것입니다.
제이슨

129
@ 제이슨-바. 그것은 황소의 짐이다. 로 교체 goto하는 return것은 어리석은 일입니다. goto억압 된 환경 에서 자란 사람들 (즉, 우리 모두)이 도덕적으로 중요한 것을 사용하는 것에 대해 더 나은 느낌을 갖도록 하기 위해 아무것도 "리팩토링"하는 것이 아니라 단지 "이름을 바꾸는"것입니다 goto. 나는 내가 그것을 사용 하는 루프를보고 싶어하고 조금만 보아라. goto그 자체가 단지 도구 일 뿐이다 goto.
크리스 루츠

18
예를 들어 예외가없는 C ++ 환경과 같이 gotos가 중요한 상황이 있습니다. Silverlight 소스에는 매크로를 사용하여 안전한 기능을위한 수만 개 이상의 goto 문이 있습니다. 주요 미디어 코덱과 라이브러리는 종종 반환 값을 통해 작동하지만 예외는 절대로 없으며 이러한 오류 처리 메커니즘을 결합하기가 어렵습니다. 단일 수행 방식.
Jeff Wilcox

79
그것의 가치에 주목 모든 것을 break, continue, return기본적으로 있습니다 goto, 단지 좋은 포장이다.
el.pescado

16
do{....}while(0)Java에서 작동한다는 사실을 제외하고는 goto보다 더 나은 아이디어가되는 방법을 보지 못했습니다 .
Jeremy 목록

906

gotoEdsger Dijkstra의 GoTo 고려 된 유해한 기사는 직간접 적으로 반박하는 모든 사람이 자신의 입장을 입증합니다. 너무 나쁜 Dijkstra의 기사는 요즘 문이 사용되는 방식과 거의 관련goto 이 없으므로 기사의 내용은 현대 프로그래밍 장면에는 거의 적용되지 않습니다. 그만큼goto 없는 밈은 이제 종교, 즉 대제사장과 지각 된 이단자들의 떨림 (또는 더 나쁘게)에서 지시 된 경전까지 거슬러 올라갑니다.

Dijkstra의 논문을 맥락에 두어 주제에 대해 약간의 조명을 줍시다.

Dijkstra가 그의 논문을 썼을 때 당시 인기있는 언어는 BASIC, FORTRAN (이전 방언) 및 다양한 어셈블리 언어와 같은 구조화되지 않은 절차 적 언어였습니다. 고급 언어를 사용하는 사람들 은 "스파게티 코드"라는 용어를 일으킨 뒤틀리고 뒤틀린 실행 스레드로 코드베이스 전체 를 뛰어 넘는 것이 일반적이었습니다 . 마이크 메이필드 (Mike Mayfield)가 쓴 클래식 트렉 (Trek) 게임 에 뛰어 들어 어떻게 작동하는지 알아 내면이 사실을 알 수 있습니다. 잠시 살펴보고 살펴보십시오.

이건 다 익스트라는 1968 년 자신의 논문에 대해 난간되었다 "문에 이동의 억제되지 않은 사용"입니다 즉 그 논문을 쓰는 그를 주도 그가 살았던 환경입니다. 원하는 시점에서 코드에서 원하는 곳으로 이동할 수있는 능력은 그가 비판하고 멈추라는 요구였습니다. 이를 gotoC 나 다른 현대 언어 의 빈약 한 힘과 비교하는 것은 간단합니다.

나는 이단자들이 직면 한 컬 티스트들의 노래를 이미들을 수있다. "하지만 gotoC에서 코드를 읽기가 매우 어려울 수 있습니다 ." 오 예? 코드 없이도 코드를 읽기 어렵게 만들 수 있습니다 goto. 이 같은:

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

a는 아니 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 프로그래머라면, 나는 #define십자군 전쟁에 나서기 전에 오래 사용하는 것의 약 50 %를 훨씬 더 가까이 들여다 보아야한다 goto!

따라서 지금까지이 책을 읽고 자하는 사람들에게는 몇 가지 중요한 사항이 있습니다.

  1. 에 다 익스트라의 논문 goto문은 프로그래밍 환경을 위해 작성되었습니다 goto했다 훨씬 더 가능성이 어셈블러없는 가장 현대적인 언어보다 손상.
  2. goto이 때문에 모든 용도를 자동으로 버리는 것은 "한 번 재미를 보려고했지만 마음에 들지 않았으므로 지금은 반대한다"고 말하는 것이 합리적입니다.
  3. goto다른 구문으로 적절하게 대체 할 수없는 코드에서 현대적인 (비유 기적) 구문을 합법적으로 사용합니다 .
  4. 물론 동일한 진술의 불법적 인 사용이 있습니다.
  5. "- godo" 대신에 항상 거짓 do루프가 사용되지 않는 " "abomination 과 같은 현대적인 제어문도 불법적 으로 사용 break됩니다 goto. 이것들은 종종 신중하게 사용하는 것보다 더 나쁩니다 goto.

42
@pocjoc : 예를 들어 C로 꼬리 재귀 최적화 작성. 이것은 기능적 언어 통역사 / 런타임 구현에서 발생하지만 흥미로운 문제 범주를 나타냅니다. C로 작성된 대부분의 컴파일러는 이러한 이유로 goto를 사용합니다. 그것은 만드는 대부분의 상황에서 발생하지 않습니다 그것의 틈새 사용은 물론,하지만 상황에 호출됩니다 많이 사용 감각을.
zxq9

66
왜 모든 사람들이 Dijkstra의 "유해한 것으로 가기"논문에 대해 Knuth의 "문장을 사용한 구조화 된 프로그래밍"에 대한 대답을 언급하지 않고 왜 이야기합니까?
Oblomov

22
@ Nietzche-jou "이것은 꽤 뻔뻔한 짚맨이다 ...] 그는 낙인이 왜 비논리적인지를 보여주기 위해 거짓 이분법을 사용하는 냉소적이다. Strawman 은 상대방의 위치가 의도적으로 잘못 표시되어 자신의 위치가 쉽게 무너지는 것처럼 보이는 논리적 오류입니다. 예를 들어 "무신론자들은 신을 미워하기 때문에 무신론자 일뿐"입니다. 잘못된 이분법 은 하나 이상의 추가 가능성 (예 : 흑백)이 존재할 때 반대 극단이 참이라고 가정 할 때입니다. 예를 들어 "하나님은 동성애자들을 미워하기 때문에 이성을 사랑합니다."
Braden Best

27
@JLRishe 당신은 거기에없는 무언가에 대해 너무 깊이 읽고 있습니다. 그것을 사용하는 사람이 그것을 논리적으로 이해한다고 믿는다면 그것은 단지 진정한 논리적 오류입니다. 그러나 그는 냉소적으로 말했다, 그것은 그것이 어리석은 것을 알고 있음을 분명히 나타내며, 이것이 그가 그것을 보여주는 방법입니다. 그는 "그의 요점을 정당화하기 위해 그것을 사용하고 있지 않다"; 그는 그것을 사용하여 gotos가 자동으로 코드를 스파게티로 만든다는 말이 우스운 이유를 보여줍니다. 즉, 고토없이 tty 한 코드를 작성할 수 있습니다. 이것이 그의 요점입니다. 그리고 냉소적 인 이분법은 그것을 유머러스하게 묘사하는 그의 방법입니다.
Braden Best

32
-1 Dijkstra의 발언이 적용되지 않는 이유에 대해 매우 큰 논쟁을 goto
펼친 이유

154

모범 사례를 맹목적으로 따르는 것은 모범 사례가 아닙니다. goto흐름 제어의 기본 형태 인 문장 을 피하는 아이디어는 읽을 수없는 스파게티 코드를 생성하지 않는 것입니다. 적절한 장소에서 드물게 사용하는 경우 아이디어를 표현하는 가장 단순하고 명확한 방법 일 수 있습니다. Zortech C ++ 컴파일러 및 D 프로그래밍 언어의 작성자 인 Walter Bright는이를 자주 사용하지만 신중하게 사용합니다. goto그의 진술 에도 불구하고 그의 코드는 여전히 완벽하게 읽을 수 있습니다.

결론 : 피하기 goto위해 피하는 goto것은 의미가 없습니다. 실제로 피하고 싶은 것은 읽을 수없는 코드를 생성하는 것입니다. 귀하의 경우 goto-laden 코드를 읽을 수있는, 다음에 아무것도 잘못이있다.


36

goto프로그램 흐름 1 (일명“스파게티 코드”) 에 대한 추론을 하기 때문에 goto일반적으로 누락 된 기능을 보상하는 데만 사용됩니다. goto실제로 사용할 수는 있지만 언어가 더 체계적인 변형을 제공하지 않는 경우에만 사용할 수 있습니다. 같은 목표. 의심의 예를 보자.

우리가 사용하는 goto의 규칙은 goto가 함수의 단일 종료 정리 지점으로 건너 뛰는 것이 좋습니다.

이는 사실이지만 언어가 정리 코드 (예 : RAII 또는 finally )를 작업을 위해 특별히 구축 된 경우와 동일) 또는 합당한 이유가없는 경우에만 가능합니다. 구조적 예외 처리를 사용합니다 (그러나 매우 낮은 수준을 제외 하고는이 경우가 없습니다).

대부분의 다른 언어에서 허용되는 유일한 사용은 goto중첩 루프를 종료하는 것입니다. 심지어 외부 루프를 자체 방법으로 들어 올려 return대신 사용하는 것이 거의 항상 좋습니다 .

그 외에는 goto특정 코드에 대해 충분히 생각하지 못했다는 표시가 있습니다.


1goto 일부 제한 사항 (예 : goto기능에 뛰어 들거나 벗어날 수 없음)을 구현 하도록 지원 하는 현대 언어 는 근본적으로 동일하게 유지됩니다.

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


4
여기서 궁금한 점이 있지만 코드 정리를 위해 gotos를 사용하는 경우는 어떻습니까? 정리하면 메모리 할당 해제뿐만 아니라 오류 로깅도 의미합니다. 나는 많은 게시물을 읽었으며 분명히 로그를 인쇄하는 코드를 작성하는 사람은 없습니다. 흠?!
shiva

37
"기본적으로 goto의 결함으로 인해 (그리고 이것이 논란의 여지가 없다고 생각합니다)"- 1 그리고 나는 거기에서 읽기를 중단했습니다.
o0 '.

14
goto에 대한 훈계는 구조화 된 프로그래밍 시대에서 비롯되었으며 초기 수익은 악으로 간주되었습니다. 중첩 루프를 함수로 옮기면 하나의 "사악한"을 다른 것으로 바꾸고, 실제 이유가없는 함수를 만듭니다 ( "goto 사용을 피할 수있었습니다!"이외). goto가 제약없이 사용될 경우 스파게티 코드를 생성하는 가장 강력한 도구라는 것은 사실이지만, 이는 완벽한 응용 프로그램입니다. 코드를 단순화합니다. Knuth는이 사용법에 대해 논쟁했으며 Dijkstra는 "고토에 대해 끔찍하게 독단적이라고 믿는 함정에 빠지지 말 것"이라고 말했다.
Mud

5
finally? 따라서 오류 처리 이외의 예외를 사용하는 것이 좋지만 사용 goto이 나쁩니 까? 나는 예외 가 상당히 적절 하다고 생각 합니다.
Christian

3
@Mud : (매일) 발생하는 경우 "실제 이유가없는"기능을 만드는 것이 가장 좋은 해결책입니다. 이유 : 결과가 읽기 쉬운 제어 흐름을 갖도록 최상위 기능에서 세부 사항을 제거합니다. goto가 가장 읽기 쉬운 코드를 생성하는 상황은 개인적으로 발생하지 않습니다. 반면에, 나는 다른 사람들이 단일 출구 점으로 가기를 선호하는 간단한 함수에서 여러 개의 리턴을 사용합니다. 잘 알려진 함수로 리팩토링하는 것보다 goto가 더 읽기 쉬운 카운터 예제를보고 싶습니다.
ToolmakerSteve

35

글쎄요, 항상 나쁜 것 하나가 있습니다 goto's; 이동을 피하기 위해 다른 프로그램 흐름 연산자의 이상한 사용 :

예 :

    // 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

2
do{}while(false)관용구로 생각할 수 있습니다. 당신은 동의 할 수 없습니다 : D
Thomas Eding

37
@trinithis : 그것이 "관념적"이라면, 그것은 안티 고토 컬트 때문입니다. 자세히 보면 goto after_do_block;실제로 말하지 않고 말하는 방법이라는 것을 알게 될 것입니다. 그렇지 않으면 ... 정확히 한 번 실행되는 "루프"? 제어 구조의 남용이라고 부릅니다.
cHao

5
@ThomasEding Eding 요점에는 한 가지 예외가 있습니다. C / C ++ 프로그래밍을하고 #define을 사용해야한다면 do {} while (0)은 여러 줄의 코드를 캡슐화하는 표준 중 하나입니다. 예를 들면 다음과 같습니다. #define do {memcpy (a, b, 1); something ++;} while (0)#define memcpy (a, b, 1)
Ignas2526

10
@ Ignas2526 당신은 한동안 #define사용하는 것보다 s가 얼마나 많은지, 몇 배나 더 나쁜지 아주 잘 보여 goto
주었습니다

3
사람들이 고 토스를 피하려고 시도하는 좋은 방법 목록을 보려면 +1하십시오. 다른 곳에서 그러한 기술에 대한 언급을 보았지만 이것은 간결하고 간결한 목록입니다. 지난 15 년 동안 우리 팀은 이러한 기술 중 어느 것도 필요하지 않았으며 고토를 사용한 적이없는 이유를 숙고합니다. 여러 프로그래머가 수십만 줄의 코드를 사용하는 클라이언트 / 서버 애플리케이션에서도 마찬가지입니다. C #, 자바, PHP, 파이썬, 자바 스크립트. 클라이언트, 서버 및 앱 코드 어떤 사람들은 왜 가장 명확한 해결책으로 고 토스를 구걸하는 상황에 직면하고 다른 사람들은 그렇지 않은지 왜 진정 궁금한지 궁금합니다.
ToolmakerSteve

28

에서 C #의 스위치도다 가을-을 통해 수 없습니다 . 따라서 goto 는 제어를 특정 스위치 케이스 레이블 또는 기본 레이블 로 전송하는 데 사용됩니다 .

예를 들면 다음과 같습니다.

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

편집 : "경과 없음"규칙에는 한 가지 예외가 있습니다. 사례 설명에 코드가없는 경우 대체가 허용됩니다.


3
스위치

9
사례에 코드 본문이없는 경우에만 코드가 없으면 goto 키워드를 사용해야합니다.
Matthew Whited

26
이 답변은 너무 재밌습니다. 많은 사람들이 해로운 것으로 간주하기 때문에 C #에서 넘어짐을 제거했습니다. 코드는 폴 스루가 의도적이라는 것을 분명히하기 때문에!).
thomasrutter

9
키워드가 GOTO라는 글자로 쓰여졌다 고해서 키워드가되지는 않습니다. 이 사례는 다루지 않습니다. 폴 스루를위한 스위치 명령문 구성입니다. 그런 다음 다시 C #을 잘 모르므로 잘못 될 수 있습니다.
Thomas Eding

1
글쎄,이 경우에는 넘어 질 것보다 조금 더 큽니다 ( goto case 5:사례 1 인 경우 말할 수 있기 때문에 ). Konrad Rudolph의 대답은 여기에 맞는 것 같습니다 : goto누락 된 기능을 보상하고 있습니다 (실제 기능보다 덜 명확합니다). 우리가 실제로 원하는 것이 실패라면, 아마도 가장 좋은 디폴트는 넘어지지 않을 것이지만, continue명시 적으로 요청하는 것입니다.
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl에는 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할 수 있습니다. .)


이것이 Perl에서 goto를 사용하는 유일한 이유라고 생각합니다.
브래드 길버트

12

나는 수년에 걸쳐 몇 줄 이상의 어셈블리 언어를 썼습니다. 궁극적으로 모든 고급 언어는 gotos로 컴파일됩니다. 좋아, "분기"나 "점프"또는 그 밖의 다른 것을 불러라. 누구나 고 토레스 어셈블러를 작성할 수 있습니까?

이제, 고토로 폭동을 일으키는 것은 스파게티 볼로냐의 요리법이라고 Fortran, C 또는 BASIC 프로그래머에게 지적 할 수 있습니다. 그러나 대답은 피하는 것이 아니라 신중하게 사용하는 것입니다.

칼은 음식을 준비하거나 누군가를 해방 시키거나 누군가를 죽이는 데 사용될 수 있습니다. 우리는 후자를 두려워하지 않고 칼을 사용하지 않습니까? 마찬가지로 goto : 부주의하게 사용하면 방해하고 조심스럽게 사용하면 도움이됩니다.


1
아마도 당신은 내가이에 근본적으로 잘못이 있다고 생각하는 이유를 읽을 수 stackoverflow.com/questions/46586/...
콘라드 루돌프

15
"어쨌든 JMP로 컴파일합니다!" 인수는 기본적으로 더 높은 수준의 언어로 프로그래밍해야 할 요점을 이해하지 못합니다.
Nietzche-jou

7
실제로 필요한 것은 빼기 및 분기입니다. 그 밖의 모든 것은 편의 또는 성능을위한 것입니다.
David Stone

8

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항상 드물게 사용하고 마지막 수단으로 사용해야합니다. 그러나 시간과 장소가 있습니다. 질문은 "사용해야합니까"가 아니라 "사용하기 가장 좋은 선택"이어야합니다.


7

코딩 스타일 외에도 goto가 나쁜 이유 중 하나는 중첩 하지만 중첩되지 않은 루프 를 만드는 데 사용할 수 있기 때문입니다 .

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

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

그냥 "아, 그건 그렇고", 컴파일러가 더 빠른 코드를 내도록 설득하는 것이 좋다는 것을 알고 있다면 유용 할 것이다 . 개인적으로, goto와 같은 트릭을 사용하기 전에 무엇이 가능하고 무엇이 아닌지에 대해 컴파일러에 설명하려고하지만 어쨌든 goto어셈블러를 해킹하기 전에 시도 할 수도 있습니다 .


3
글쎄 ... 금융 정보 회사에서 FORTRAN을 프로그래밍하던 시절로 되돌아 가겠습니다. 2007 년.
Marcin

5
모든 언어 구조는 읽을 수 없거나 성능이 떨어지는 방식으로 남용 될 수 있습니다.
저의 올바른 의견

@JUST : 요점은 가독성이나 성능 저하가 아니라 제어 흐름 그래프에 대한 가정과 보증입니다. 고토의 남용이 될 것이다 증가 성능 (또는 가독성).
Anders Eurenius

3
나는 이유 중 하나가 주장하는 것 goto입니다 유용 당신이 그렇지 않으면 논리적 뒤틀림의 무리를 필요로 같은 루프를 구성 할 수 있다는 것입니다. 또한 옵티마이 저가 이것을 다시 쓰는 방법을 모른다면 good 이라고 주장합니다 . 이와 같은 루프는 성능이나 가독성을 위해 수행해서는 안되지만 정확히 발생하는 순서이기 때문입니다. 어떤 경우에는 특히 옵티마이 저가 나사로 조이기를 원하지 않습니다 .
cHao

1
... 필요한 알고리즘을 GOTO 기반 코드로 간단하게 변환하는 것은 플래그 변수의 혼란과 악용 된 루핑 구문보다 검증하기가 훨씬 쉽습니다.
supercat

7

나는 어떤 사람들은 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가 유용한 모든 경우를 아는 척하지 마십시오. 생각하지 않습니다.


1
당신이 준 예에서, 나는 그것을 상당히 크게 리팩토링하고 싶습니다. 일반적으로 다음 코드 덩어리가하는 일을 말하는 한 줄짜리 주석을 피하려고합니다. 대신, 주석과 비슷한 이름의 자체 기능으로 분리합니다. 이러한 변환을 수행하는 경우이 함수는 함수가 수행하는 작업에 대한 높은 수준의 개요를 제공하며 새로운 각 함수는 각 단계를 수행하는 방법을 나타냅니다. goto각 기능이 동일한 추상화 수준에 있도록 하는 것이 어떤 반대보다 훨씬 중요하다고 생각합니다 . 피하는 goto것은 보너스입니다.
David Stone

2
더 많은 함수를 추가하는 것이 어떻게 goto, 여러 들여 쓰기 레벨 및 가짜 if 문을 제거하는지 설명하지 않았습니다 ...
Ricardo

표준 컨테이너 표기법을 사용하여 다음과 같이 보일 것 container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();입니다. 프로그래밍이 훨씬 읽기 쉽습니다. 이 경우 각 함수는 순수한 함수로 작성 될 수 있으며 추론하기가 훨씬 쉽습니다. 이제 주 함수는 함수 이름으로 코드의 기능을 설명한 다음 원하는 경우 해당 정의를보고 코드의 기능을 확인할 수 있습니다.
David Stone

3
어떤 알고리즘이 자연스럽게 goto를 사용해야하는 방법에 대한 예를 누군가가 제시해야한다는 사실은 오늘날의 알고리즘 사고가 얼마나 진행되는지에 대한 슬픈 반영입니다 !! 물론 @Ricardo의 예는 goto가 우아하고 명백한 곳의 완벽한 예입니다.
Fattie

6

우리가 사용하는 goto의 규칙은 goto가 함수의 단일 종료 정리 지점으로 건너 뛰는 것이 좋습니다. 실제로 복잡한 기능에서는 다른 규칙으로 넘어갈 수 있도록이 규칙을 완화합니다. 두 경우 모두 오류 코드 검사와 함께 자주 발생하는 중첩 된 if 문을 피하여 가독성과 유지 관리에 도움이됩니다.


1
C와 같은 언어에서 그와 같은 것이 유용하다는 것을 알 수 있습니다. 그러나 C ++ 생성자 / 소멸자의 힘이 있으면 일반적으로 그렇게 유용하지 않습니다.
David Stone

1
"정말 복잡한 함수에서는 다른 규칙을 앞으로 넘길 수 있도록 규칙을 완화합니다." 예를 들어 점프를 사용하여 복잡한 함수를 더 복잡하게 만드는 경우와 같은 소리가납니다. 복잡한 기능을 리팩토링하고 분할하는 "더 나은"접근 방법이 아닌가?
MikeMB

1
이것이 내가 goto를 사용한 마지막 목적이었습니다. Java에서 goto를 놓치지 마십시오. 마지막으로 같은 일을 할 수있는 시도가 있기 때문입니다.
Patricia Shanahan 2016 년

5

도널드 크 누스 (Donald Knuth)의 기사 " 고토 문 (goto statements)을 이용한 구조화 된 프로그래밍 (Structured Programming with goto Statements) " , 1974 년 12 월 Computing Surveys (6 권 4 호 261-301 페이지).

놀랍게도,이 39 년 된 논문의 일부 측면은 날짜가 있습니다 : 처리 능력의 크기가 증가하면 Knuth의 성능 향상 중 일부가 적당한 규모의 문제에 대해 눈에 띄지 않게되고 그 이후로 새로운 프로그래밍 언어 구성물이 발명되었습니다. (예를 들어, try-catch 블록은 Zahn의 Construct를 거의 사용하지 않지만 Subsume Zahn 's Construct를 대신합니다.) 그러나 Knuth는 인수의 모든 측면을 다루므로 누군가가 문제를 다시 해시하기 전에 읽어야합니다.


3

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을 사용하여 기본적인 형태의 테일 콜 최적화를 제공 할 수 있습니다 .

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

  return $tally if $n <= 1;

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

( Perl 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
  ...
}

2

그렇다면 왜 그렇습니까?

C에는 다중 레벨 / 레이블 구분이 없으며 모든 제어 플로우를 C의 반복 및 의사 결정 기본 요소로 쉽게 모델링 할 수있는 것은 아닙니다. gotos는 이러한 결함을 해결하기 위해 먼 길을갑니다.

때때로 의사 멀티 레벨 브레이크의 종류에 영향을 미치기 위해 일종의 플래그 변수를 사용하는 것이 더 명확하지만 항상 goto보다 우월하지는 않습니다 (적어도 goto는 플래그 변수와 달리 제어가 어디로 가는지 쉽게 결정할 수 있습니다) ), 때로는 이동을 피하기 위해 플래그 / 기타 왜곡의 성능 가격을 지불하고 싶지 않습니다.

libavcodec는 성능에 민감한 코드입니다. 제어 흐름을 직접 표현하는 것이 우선 순위가 더 높을 수 있습니다.


2

"COME FROM"문을 구현 한 사람은 없습니다.


8
또는 C ++, C #, Java, JS, Python, Ruby 등에서 "예외"라고합니다.
cHao

2

do {} while (false) 사용법이 완전히 반란을 일으 킵니다. 이상한 경우가 필요하다는 것을 확신 할 수는 있지만 깨끗한 코드는 결코 아닙니다.

이러한 루프를 수행 해야하는 경우 플래그 변수에 대한 의존성을 명시 적으로 지정하지 않겠습니까?

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

하지 않는 것이이해야 /*empty*/stepfailed = 1? 어쨌든 이것이 어떻게 A보다 낫 do{}while(0)습니까? 둘 다, 당신 break은 그것에서 (또는 당신의 stepfailed = 1; continue;) 밖으로 해야합니다 . 나에게 불필요 해 보인다.
Thomas Eding

2

1) 내가 아는 goto의 가장 일반적인 사용법은 예외 처리를 제공하지 않는 언어, 즉 C에서 예외 처리를 모방하는 것입니다. bazillion gotos가 그런 식으로 사용되는 것을 볼 수 있습니다. http://blog.regehr.org/archives/894 : 2013 년에 실시 된 빠른 설문 조사에 따르면 Linux 코드에는 약 10 만 건이있었습니다 . Goto 사용법은 Linux 코딩 스타일 가이드 ( https://www.kernel.org/doc/Documentation/CodingStyle )에서도 언급됩니다 . 함수 포인터로 채워진 구조체를 사용하여 객체 지향 프로그래밍을 에뮬레이션하는 것처럼 goto는 C 프로그래밍에서 그 위치를 차지합니다. Dijkstra 또는 Linus (및 모든 Linux 커널 코더)는 누구입니까? 기본적으로 이론과 연습입니다.

그러나 컴파일러 수준의 지원을받지 않고 일반적인 구성 / 패턴을 검사하지 않는 일반적인 문제가 있습니다. 잘못 사용하는 것이 더 쉽고 컴파일 타임 검사없이 버그를 도입 할 수 있습니다. Windows 및 Visual C ++이지만 C 모드에서는 SEH / VEH를 통한 예외 처리가 바로 이런 이유로 제공됩니다. 예외는 OOP 언어 외부 (예 : 절차 언어)에서도 유용합니다. 그러나 컴파일러는 언어에서 예외에 대한 구문 지원을 제공하더라도 베이컨을 항상 저장할 수는 없습니다. 후자의 예를 들어 유명한 Apple SSL "goto fail"버그를 고려 하십시오 .

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 구문이 계산 된 주소 (**)에 대한 gotos와 같이 컴파일러가 쉽게 알아낼 수없는 대상을 허용하지 않는 한 Dijkstra를 사용하는 것보다 gotos가있는 함수에서 컴파일러가 도달 할 수없는 코드를 찾는 것이 더 이상 어렵지 않습니다. 승인 코드.

(**) 각주 : 계산 된 라인 번호로 이동은 일부 버전의 Basic에서 가능합니다 (예 : x는 변수 인 GOTO 10 * x). 오히려 Fortran에서 "computed goto"는 C의 switch 문과 동일한 구문을 의미합니다. 표준 C는 언어에서 계산 된 gotos를 허용하지 않지만 정적 / 구문 적으로 선언 된 레이블에만 gotos를 허용합니다. 그러나 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 () 쌍의 함수를 제공하지만 다른 언어와 비교할 때 심각한 단점이 있습니다. Wikipedia 기사 http://en.wikipedia.org/wiki/Setjmp.h 는이 후자의 문제를 상당히 잘 설명합니다. 이 함수 쌍은 Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ) 에서도 작동 하지만 SEH / VEH가 우수하기 때문에이 기능을 사용하는 사람은 거의 없습니다. 유닉스에서도 setjmp와 longjmp는 거의 사용되지 않습니다.

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

추가하기 위해 편집 :이 두 가지 사용 패턴은 Kernighan 및 Ritchie의 C 책, 60 페이지 (판에 따라 다름)에서 발견됩니다. 주목해야 할 또 다른 사항은 두 유스 케이스 모두 순방향 고스 토스 만 포함한다는 것입니다. 그리고 MISRA C 2012 에디션 (2004 에디션과 달리)은 앞으로 진행되는 한 고 토스를 허용합니다.


권리. "방에있는 코끼리"는 세계적으로 중요한 코드 기반의 한 예로 Linux 커널에 goto가로드되어 있다는 것입니다. 당연하지. 명백하게. "anti-goto-meme"은 수십 년 전의 호기심입니다. 과정 중에는 비전문가가 남용 할 수있는 프로그래밍 (특히 "정적", 실제로는 "전역"및 "elseif")이 많이 있습니다. 따라서, 만약 당신이 사촌 사촌 2 학습 프로그램이라면, "오 글로벌을 사용하지 마십시오"와 "다른 사람은 절대 사용하지 마십시오"라고 말합니다.
Fattie

goto fail 버그는 goto와 관련이 없습니다. 문제는 나중에 중괄호가없는 if 문으로 인해 발생합니다. 두 번 붙여 넣은 명령문 사본은 거의 문제를 일으켰습니다. 나는 goto보다 훨씬 해롭다면 벌거 벗은 끈이없는 유형을 고려합니다.
muusbolla 2016 년

2

어떤 이들은 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;

고토 프리 코드와 비교하십시오.

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?
  }
}

이제 "악"고토를 제거해 봅시다 :

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 자체는 책임이 없다는 것입니다. 레이블은 C ++에서 함수 범위를 가지므로 , 7segment 디스플레이가 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:;

1

Perl에서, 레이블을 사용하여 루프에서 "goto"- "last"문을 사용하십시오. 이는 break와 유사합니다.

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

전통적인 goto 레이블 도 지원되지만 이것이 원하는 것을 달성하는 유일한 방법 인 서브 인스턴스와 루프가 충분하다는 것은 확실하지 않습니다. 대부분의 경우 서브 루틴과 루프가 충분해야합니다.


나는 당신이 Perl에서 사용할 유일한 형태의 goto라고 생각합니다 goto &subroutine. 스택에서 현재 서브 루틴을 바꾸는 동안 서브 루틴을 현재 _로 시작합니다.
브래드 길버트

1

'goto'의 문제점과 'goto-less programming'운동의 가장 중요한 논점은 코드를 너무 자주 사용하면 코드가 올바르게 작동하더라도 읽을 수없고, 유지할 수없고, 확인할 수없는 등이된다는 것입니다. 'goto'사례는 스파게티 코드로 연결됩니다. 개인적으로, 나는 왜 'goto'를 사용할 것인지에 대한 정당한 이유를 생각할 수 없습니다.


11
" 상상을 생각할 수 없다"고 말하는 것은 공식적으로 en.wikipedia.org/wiki/Argument_from_ignorance입니다 . 비록 "상상력이없는 증거"라는 용어를 선호합니다.
저의 올바른 의견 그냥

2
@ 내 올바른 의견을 생각하십시오 : 사후에 고용 된 경우에만 논리적 오류입니다. 언어 디자이너의 관점에서 볼 때 기능 ( goto) 을 포함하는 비용을 측정하는 데 유효한 논거가 될 수 있습니다 . @cschol의 사용법은 비슷합니다. 현재 언어를 디자인하지 않았지만 기본적으로 디자이너의 노력을 평가하고 있습니다.
Konrad Rudolph

1
@KonradRudolph : IMHO, 언어를 허용 goto하면 변수가 존재하는 상황을 제외하고는 누군가가 필요할 수있는 모든 종류의 제어 구조를 지원하는 것보다 저렴합니다. 코드를 작성 goto하는 것은 다른 구조를 사용하는 것만 큼 좋지는 않지만 그러한 코드를 작성 goto하는 것은 "표현의 허점"을 피하는 데 도움 이 됩니다. 언어가 효율적인 코드를 작성할 수없는 구조입니다.
supercat

1
@supercat 저는 기본적으로 다른 언어 디자인 학교에서 온 것이 두렵습니다. 나는 이해력 (또는 정확성)의 가격으로 언어를 최대한 표현하는 것에 반대한다. 오히려 허용 언어보다 제한이 있습니다.
Konrad Rudolph

1
@thb 예 물론입니다. 종종 코드 를 읽고 이해하기가 훨씬 어렵습니다. 실제로 누군가 goto가 코드 검토 사이트에 포함 된 코드를 게시 할 때마다 goto코드의 논리 가 크게 단순화됩니다.
Konrad Rudolph

1

물론 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)

우리가 생각하는 첫 번째는 두 코드 비트의 결과가 "Value of A : 0"이 될 것입니다 (물론 우리는 병렬 처리없이 실행한다고 가정합니다)

맞지 않습니다. 첫 번째 샘플에서 A는 항상 0이지만 두 번째 샘플 (GOTO 문 포함)에서는 A가 0이 아닐 수 있습니다. 왜 그렇습니까?

그 이유는 프로그램의 다른 지점 GOTO FINAL에서 A 값을 제어하지 않고를 삽입 할 수 있기 때문 입니다.

이 예는 매우 분명하지만 프로그램이 복잡 해짐에 따라 그러한 종류의 것을보기가 어려워집니다.

관련 자료는 Dijkstra 씨의 유명한 기사 "GO TO 진술에 대한 사례" 에서 확인할 수 있습니다 .


6
구식 BASIC의 경우가 종종있었습니다. 그러나 현대 변형에서는 다른 함수의 중간으로 또는 많은 경우 변수 선언을 넘어서서는 안됩니다. 기본적으로 (의도 된 의도는 아님), 현대 언어는 Dijkstra가 말한 "고정되지 않은"GOTO를 사용하여 대부분 제거했습니다. :)
cHao

1

다음과 같은 경우 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;     
}

두 번째 버전은 할당 해제 명령문에서 무언가를 변경해야 할 때 (각각 코드에서 한 번 사용됨) 더 쉬워지고 새 분기를 추가 할 때 생략 할 가능성을 줄입니다. 할당 해제가 다른 "레벨"에서 수행 될 수 있기 때문에 함수로 이동하면 도움이되지 않습니다.


3
이것이 finallyC #에 블록 이있는 이유입니다
John Saunders

^ 같은 @JohnSaunders 말했다. 이것은 "언어에 적절한 제어 구문이 없기 때문에 goto를 사용하는"예입니다. 그러나 리턴 근처에 MULTIPLE goto 포인트를 요구하는 것은 코드 냄새입니다. 이 안전 프로그래밍 스타일 (쉽게는 망치지하는)이다 AND "마지막으로"하지 않고도 언어로 잘 작동하는 gotos를 필요로하지 않는다 : 그들은 때 무 세척 할 해가되도록 그 "청소"전화를 디자인 업이 필요합니다. 정리를 제외한 모든 요소를 ​​다중 반환 설계를 사용하는 방법으로 고려합니다. 해당 메소드를 호출 한 후 정리 호출을 수행하십시오.
ToolmakerSteve

내가 설명하는 접근법에는 추가 수준의 메소드 호출이 필요합니다 (그러나 언어가 부족한 언어에서만 finally). 대안으로 gotos를 사용 하지만 항상 모든 정리 를 수행하는 공통 종료점 을 사용하십시오. 그러나 각 정리 방법은 null이거나 이미 깨끗한 값을 처리 할 수 ​​있거나 조건부 테스트로 보호되므로 적절하지 않은 경우 건너 뜁니다.
ToolmakerSteve

@ToolmakerSteve 이것은 코드 냄새가 아닙니다. 실제로 C에서 매우 일반적인 패턴이며 goto를 사용하는 가장 유효한 방법 중 하나로 간주됩니다. 이 함수에서 정리를 처리하기 위해 각각 고유 한 불필요한 if-test가있는 5 개의 메소드를 작성 하시겠습니까? 이제 코드와 성능이 크게 향상되었습니다. 아니면 그냥 goto를 사용할 수도 있습니다.
muusbolla 2016 년

@muusbolla-1) "5 가지 메소드 생성"-아니오 . null이 아닌 값을 가진 모든 리소스를 정리 하는 단일 메소드를 제안 합니다. 또는 똑같은 논리 점 goto가진 동일한 종료점으로 가는 s 를 사용하는 대안을 참조하십시오 (이것은 리소스마다 if가 필요합니다). 그러나 C코드를 C로 사용하는 이유가 무엇이든간에 가장 올바른 "직접적인"코드를 선호하는 트레이드 오프는 당신이 옳을 때 사용 하는 것이 좋습니다. (나의 제안은 주어진 자원이 할당되거나 할당되지 않은 복잡한 상황을 처리합니다. 그러나이 경우에는 과잉입니다.)
ToolmakerSteve

0

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


0

문자별로 문자열을 처리 할 때 유용합니다.

이 printf-esque 예제와 같은 것을 상상해보십시오.

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

goto handle_literal이를 함수 호출로 리팩터링 할 수 있지만, 여러 다른 로컬 변수를 수정하는 경우 언어가 변경 가능한 클로저를 지원하지 않는 한 각각에 대한 참조를 전달해야합니다. 당신은 여전히continue 당신의 논리가 다른 경우가 아니라 일을 만드는 경우 동일한 의미를 얻기 위해 호출 후 (틀림없이 고토의 한 형태 인) 문을.

나는 또한 비슷한 경우를 위해 어휘 분석기에서 gotos를 신중하게 사용했다. 당신은 그것들을 대부분 필요로하지는 않지만, 그런 이상한 경우에는 좋은 것입니다.

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