C 또는 C ++에서 좋은 gotos의 예 [닫힌]


79

이 스레드에서 우리 goto는 C 또는 C ++ 의 좋은 사용 예를 살펴 봅니다 . 사람들이 내가 농담이라고 생각했기 때문에 투표 한 답변 에서 영감을 얻었 습니다.

요약 (의도를 더 명확하게하기 위해 라벨이 원본에서 변경됨) :

infinite_loop:

    // code goes here

goto infinite_loop;

대안보다 나은 이유 :

  • 구체적입니다. goto무조건 분기를 일으키는 언어 구조입니다. 대안은 항상 참인 조건이 퇴화 된 조건부 분기를 지원하는 구조를 사용하는 것에 의존합니다.
  • 레이블은 추가 설명없이 의도를 문서화합니다.
  • 독자는 초기 breaks를 위해 중간 코드를 스캔 할 필요가 없습니다 (비 원칙적인 해커가 초기 s로 시뮬레이션 continue하는 것은 여전히 가능합니다 goto).

규칙 :

  • gotophobes가 이기지 않은 척하십시오. 위의 내용은 기존 관용구에 위배되기 때문에 실제 코드에서 사용할 수 없습니다.
  • 우리 모두가 '유해하다고 생각한 Goto'에 대해 들어 봤고 goto를 사용하여 스파게티 코드를 작성할 수 있다는 것을 알고 있다고 가정합니다.
  • 예에 동의하지 않는 경우 기술적 장점만으로 비판하십시오 ( '사람들이 고토를 좋아하지 않기 때문에'는 기술적 이유가 아닙니다).

우리가 어른처럼 이야기 할 수 있는지 봅시다.

편집하다

이 질문은 이제 끝난 것 같습니다. 고품질 답변을 생성했습니다. 모든 사람, 특히 내 작은 루프 예제를 진지하게 받아 들인 사람들에게 감사합니다. 대부분의 회의론자들은 블록 범위의 부족을 우려했습니다. @quinmars가 주석에서 지적했듯이 언제든지 루프 본문을 중괄호로 묶을 수 있습니다. 그 전달에주의 for(;;)하고 while(true)(애 태우게하는 버그를 일으킬 수 있으며이를 생략) 당신이 중 하나를 무료로 중괄호를 제공하지 않습니다. 어쨌든,이 사소한 일에 더 이상 당신의 두뇌 전력의 낭비하지 않습니다 - 나는 무해하고 관용적으로 살 수 for(;;)while(true)(단지뿐만 아니라 내가 내 일을 유지하려는 경우).

다른 응답을 고려할 때 많은 사람들 goto이 항상 다른 방식으로 다시 작성해야하는 것으로 간주하는 것을 봅니다 . 물론 goto루프, 추가 플래그, 중첩 된 ifs 스택 등을 도입 하여을 피할 수 있지만 goto작업에 가장 적합한 도구 인지 고려하지 않는 이유 는 무엇입니까? 다시 말해, 의도 된 목적을 위해 내장 된 언어 기능을 사용하는 것을 피하기 위해 사람들이 얼마나 추악함을 견딜 준비가되어 있습니까? 내 생각은 깃발을 추가하는 것조차도 지불하기에는 너무 비싸다는 것입니다. 나는 내 변수가 문제 또는 솔루션 영역에서 사물을 나타내는 것을 좋아합니다. '오로지 피하기 위해 goto'는 그것을 자르지 않습니다.

정리 블록으로 분기하기 위해 C 패턴을 부여한 첫 번째 답변을 수락합니다. IMO, 이것은 goto게시 된 모든 답변 중 가장 강력한 경우를 만듭니다 .


11
Gotophobes가 프로젝트의 포함 파일 상단에 "#define goto report_to_your_supervisor_for_re_education_through_labour"를 명령하지 않는 이유를 알 수 없습니다. 항상 틀렸다면 불가능하게 만드세요. 그렇지 않으면 때로는 옳습니다 ...
Steve Jessop

14
"실제 코드에서 사용할 수 없습니다"? 작업에 가장 적합한 도구가 될 때마다 "실제"코드로 사용합니다. "기존 관용구"는 "맹인 독단주의"에 대한 멋진 완곡 어입니다.
Dan Molding

7
"goto"가 유용 할 수 있다는 데 동의하지만 (아래에 훌륭한 예가 있음) 특정 예에 동의하지 않습니다. "goto infinite_loop"라는 줄은 "initialize (); set_things_up (); goto infinite_loop;"에서와 같이 "우리가 영원히 루프를 시작할 코드 부분으로 이동"을 의미하는 것처럼 들립니다. 당신이 정말로 전달하고자하는 것은 "우리가 이미있는 루프의 다음 반복을 시작하라"는 것인데, 이것은 완전히 다릅니다. 반복을 시도하고 있고 언어에 반복을 위해 특별히 설계된 구조가있는 경우 명확성을 위해 사용하십시오. while (true) {foo ()}는 매우 명확합니다.
Josh

6
귀하의 예를 한 가지 큰 단점은 어떤 명백한되지 않는 것입니다 ohter 코드의 장소는이 라벨로 이동하기로 결정할 수있다. "일반"무한 루프 ( for (;;))에는 놀라운 진입 점이 없습니다.
jalf 2011 년

3
@ György : 네,하지만 그것은 진입 점이 아닙니다.
jalf 2011 년

답변:


87

내가 사람들이 사용하는 것에 대해 들었던 한 가지 트릭이 있습니다. 나는 그것을 야생에서 본 적이 없다. 그리고 C ++에는 더 관용적으로이 작업을 수행하는 RAII가 있기 때문에 C에만 적용됩니다.

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}

2
이것에 무슨 문제가 있습니까? (주석이 줄 바꿈을 이해하지 못한다는 사실을 제외하고) void foo () {if (doA ()) {if (doB ()) {if (doC ()) {/ * 모든 것이 성공했습니다 * / return; } undoB (); } undoA (); } 반환; }
Artelius

6
추가 블록은 읽기 어려운 불필요한 들여 쓰기를 유발합니다. 조건 중 하나가 루프 내부에있는 경우에도 작동하지 않습니다.
Adam Rosenfield

26
이러한 종류의 코드는 대부분의 저수준 Unix에서 볼 수 있습니다 (예 : Linux 커널). C에서는 IMHO 복구 오류에 대한 최고의 관용구입니다.
David Cournapeau

8
cournape가 언급했듯이 Linux 커널은 항상 이 스타일을 사용합니다 .
CesarB

8
Linux 커널뿐만 아니라 Microsoft의 Windows 드라이버 샘플을 살펴보면 동일한 패턴을 찾을 수 있습니다. 일반적으로 이것은 예외를 처리하는 C 방식이며 매우 유용한 방식입니다. :). 나는 일반적으로 1 개의 라벨만을 선호하고 몇몇 경우 2 개를 선호합니다. 3 개는 99 %의 케이스에서 피할 수 있습니다.
Ilya

85

C에서 GOTO에 대한 고전적인 요구는 다음과 같습니다.

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

goto없이 중첩 된 루프에서 벗어나는 간단한 방법은 없습니다.


49
대부분의 경우 이러한 요구가 발생하면 대신 반품을 사용할 수 있습니다. 하지만 그렇게하려면 작은 함수를 작성해야합니다.
Darius Bacon

7
저는 Darius에 확실히 동의합니다. 함수로 리팩토링하고 대신 반환합니다!
metao

9
@metao : Bjarne Stroustrup은 동의하지 않습니다. 그의 C ++ 프로그래밍 언어 책에서 이것은 goto의 "좋은 사용"에 대한 예입니다.
paercebal

19
@Adam Rosenfield : Dijkstra가 강조한 goto 의 전체 문제 는 구조화 된 프로그래밍이 더 이해하기 쉬운 구조를 제공한다는 것입니다. goto를 피하기 위해 코드를 읽기 어렵게 만드는 것은 저자가 그 에세이를 이해하지 못했음을 증명합니다.
Steve Jessop

5
@Steve Jessop : 사람들이 에세이를 오해하게 만들었을뿐만 아니라 대부분의 "no go to"이교도들은 그것이 쓰여진 역사적 맥락을 이해하지 못합니다.
그냥 내 정확한 OPINION

32

다음은 신호에 의해 중단 될 수있는 Unix 시스템 호출에 대한 어리석지 않은 예제 (Stevens APITUE의)입니다.

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

대안은 퇴화 루프입니다. 이 버전은 영어 "시스템 호출이 신호에 의해 중단 된 경우 다시 시작하십시오"와 같이 표시됩니다.


3
이 방법은 예를 들어 리눅스 스케줄러가 이와 같은 goto를 가지고 있지만 역방향 goto가 허용되고 일반적으로 피해야하는 경우는 거의 없다고 말하고 싶습니다.
Ilya

8
@Amarghosh, continue그냥 goto마스크입니다.
jball 2010-06-03

2
@jball ... hmm .. 인수를 위해, 예; 계속하면 goto와 spaghetti 코드로 읽기 좋은 코드를 만들 수 있습니다. 궁극적으로 코드를 작성하는 사람에 따라 다릅니다. 요점은 계속하는 것보다 goto로 길을 잃기 쉽다는 것입니다. 그리고 초보자는 일반적으로 모든 문제에 대해 얻은 첫 번째 망치를 사용합니다.
Amarghosh 2010 년

2
@Amarghosh. '개선 된'코드는 원본과 동일하지도 않습니다. 성공한 경우 영원히 반복됩니다.
fizzer 2010-06-04

3
@fizzer oopsie .. else break동등하게 만들려면 두 개의 s (각 ifs에 대해 하나씩)가 필요합니다. 내가 무엇을 말할 수 있는지 ... goto또는 말하지 않아도 코드는 당신만큼 좋습니다 :(
Amarghosh

14

Duff의 장치에 goto가 필요하지 않으면 당신도 마찬가지입니다! ;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

위키 백과 항목 에서 위의 코드 .


2
Cmon 여러분, 당신이 반대표를 던지는 경우, 왜 좋을지에 대한 짧은 코멘트. :)
Mitch Wheat

10
이것은 "권한에 대한 호소"라고도 알려진 논리적 오류의 예입니다. Wikipedia에서 확인하십시오.
Marcus Griep

11
유머는 여기에서 종종 감사하지 않습니다 ...
Steven A. Lowe

8
더프의 장치가 맥주를 만들어요?
Steven A. Lowe

6
이것은 한 번에 8 개를 만들 수 있습니다 ;-)
Jasper Bekkers

14

Knuth는 "GOTO 문을 사용한 구조화 된 프로그래밍"이라는 논문을 작성했습니다. 예를 들어 여기 에서 얻을 수 있습니다 . 거기에서 많은 예를 찾을 수 있습니다.


이 논문은 깊어 보입니다. 나는 그것을 읽을 것이다. 감사합니다
fizzer

2
그 논문은 너무 오래되어 재미도 없습니다. Knuth의 가정은 더 이상 유지되지 않습니다.
Konrad Rudolph

3
어떤 가정? 그의 예는 당시와 마찬가지로 C (그는 의사 코드로 제공)와 같은 절차 적 언어에 대한 실제적인 예입니다.
zvrba

8
IM은 당신이 쓰지 않았다고 실망했습니다 :: 당신은 그것을 얻기 위해 '여기'로 갈 수 있습니다.
RhysW 2013 년

12

매우 흔한.

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

내가 사용하는 유일한 경우 goto는 앞으로 점프하는 것입니다. 일반적으로 블록에서 벗어나 블록으로 들어 가지 않습니다. 이렇게하면 do{}while(0)읽기 가능하고 구조화 된 코드를 유지하면서 중첩을 증가시키는 기타 구문의 남용을 방지 할 수 있습니다.


5
이것이 오류 처리의 전형적인 C 방식이라고 생각합니다. 나는 그것이 멋지게 대체 된 것을 볼 수없고, 다른 방법으로 더 잘 읽을 수 있습니다. 감사합니다
Friedrich

왜 ... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
Петър Петров

1
@ ПетърПетров C에서 아날로그가없는 C ++의 패턴입니다.
ephemient

또는 C ++보다 훨씬 더 구조화 된 언어이지만 try { lock();..code.. } finally { unlock(); }C는 이에 상응하는 것이 없습니다.
Blindy

12

나는 일반적으로 gotos에 반대하는 것이 없지만 언급 한 것처럼 루프에 사용하지 않는 몇 가지 이유를 생각할 수 있습니다.

  • 범위를 제한하지 않으므로 내부에서 사용하는 임시 변수는 나중에까지 해제되지 않습니다.
  • 범위를 제한하지 않으므로 버그가 발생할 수 있습니다.
  • 범위를 제한하지 않으므로 나중에 동일한 범위의 코드에서 동일한 변수 이름을 다시 사용할 수 없습니다.
  • 범위를 제한하지 않으므로 변수 선언을 건너 뛸 수 있습니다.
  • 사람들은 그것에 익숙하지 않으며 코드를 읽기 어렵게 만듭니다.
  • 이 유형의 중첩 루프는 스파게티 코드로 이어질 수 있으며 노멀 루프는 스파게티 코드로 이어지지 않습니다.

1
is inf_loop : {/ * loop body * /} goto inf_loop; 보다 나은? :)
quinmars

2
이 예 에서는 중괄호가 없어도 1-4 점은 모호 합니다 . 1 무한 루프입니다. 2 너무 모호해서 다루기 힘들다. 3 둘러싸는 범위에서 같은 이름의 변수를 숨기려고하는 것은 좋지 않습니다. 4. 뒤로 이동은 선언을 건너 뛸 수 없습니다.
fizzer

1-4 모든 무한 루프와 마찬가지로 일반적으로 중간에 일종의 중단 조건이 있습니다. 그래서 그들은 적용 가능합니다. Re a bakward goto는 선언을 건너 뛸 수 없습니다 ... 모든 gotos가 거꾸로되는 것은 아닙니다 ...
Brian R. Bondy

7

goto를 사용하기에 좋은 곳 중 하나는 여러 지점에서 중단 할 수있는 절차에 있으며, 각 지점에는 다양한 수준의 정리가 필요합니다. Gotophobes는 항상 구조화 된 코드와 일련의 테스트로 gotos를 대체 할 수 있지만 과도한 들여 쓰기를 제거하기 때문에 이것이 더 간단하다고 생각합니다.

if (! openDataFile ())
  goto quit;

if (! getDataFromFile ())
  closeFileAndQuit로 이동;

if (! allocateSomeResources)
  freeResourcesAndQuit로 이동하십시오.

// 여기서 더 많은 작업을 수행합니다 ....

freeResourcesAndQuit :
   // 무료 리소스
closeFileAndQuit :
   // 파일 닫기
떠나다:
   // 종료!

: 나는 함수의 집합으로이를 대체 할 freeResourcesCloseFileQuit, closeFileQuit하고 quit.
RCIX

4
이 3 개의 추가 함수를 만든 경우 해제 / 닫을 리소스 및 파일에 포인터 / 핸들을 전달해야합니다. 당신은 아마도 freeResourcesCloseFileQuit ()가 closeFileQuit ()를 호출하도록 만들 것이고, 이것은 차례로 quit ()를 호출 할 것입니다. 이제 유지해야 할 밀접하게 결합 된 4 개의 함수가 있으며 그중 3 개는 위의 단일 함수에서 최대 한 번에 호출 될 것입니다. goto, IMO, 중첩 된 if () 블록은 오버 헤드가 적고 읽기 및 유지 관리가 더 쉽습니다. 3 가지 추가 기능으로 무엇을 얻습니까?
Adam Liss

7

@ fizzer.myopenid.com : 게시 된 코드 스 니펫은 다음과 같습니다.

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

나는 확실히이 형태를 선호한다.


1
좋습니다. break 문이 더 수용 가능한 goto 형식이라는 것을 압니다.
Mitch Wheat

10
내 눈에는 혼란 스럽습니다. 정상적인 실행 경로에 들어갈 것으로 예상하지 않는 루프이므로 논리가 거꾸로 보입니다. 그래도 의견 문제.
fizzer

1
뒤로? 시작되고 계속되고 넘어집니다. 나는이 형태가 그 의도를 더 분명히 표현한다고 생각합니다.
Mitch Wheat

8
나는 goto가 조건의 가치에 대해 더 명확한 기대를 제공한다는 fizzer에 동의합니다.
Marcus Griep

4
어떤면에서이 스 니펫은 fizzer보다 읽기 쉽습니다.
erikkallen

6

시간이 지남에 따라이 패턴을 싫어하게되었지만 COM 프로그래밍에 뿌리를두고 있습니다.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}

5

다음은 좋은 goto의 예입니다.

// No Code

2
어, 안돼? (재미있는 시도 실패 : d)
moogs

9
이건 정말 도움이되지 않습니다
조셉

재미 있다고 생각 했어요. 잘 했어. +1
Fantastic Mr Fox

@FlySwat 참고 : 품질이 낮은 검토 대기열에서 발견했습니다. 편집을 고려하십시오.
Paul

1

goto가 올바르게 사용되는 것을 보았지만 상황은 일반적으로 추악합니다. 그 goto자체 의 사용 이 원본보다 훨씬 더 나쁠 때만 가능합니다 . @Johnathon Holland는 당신이 버전이 덜 명확하다는 것입니다. 사람들은 지역 변수를 두려워하는 것 같습니다.

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

그리고 나는 이와 같은 루프를 선호하지만 어떤 사람들은 선호합니다 while(true).

for (;;)
{
    //code goes here
}

2
절대로 부울 값을 true 또는 false와 비교하지 마십시오. 이미 부울입니다. "doBsuccess = doAsuccess && doB ();"를 사용하십시오. 대신 "if (! doCsuccess) {}"가 있습니다. 더 간단하고 "#define true 0"과 같은 것을 작성하는 영리한 프로그래머로부터 사용자를 보호합니다. 왜냐하면 그들이 할 수 있기 때문입니다. 또한 int와 bool을 혼합 한 다음 0이 아닌 값이 참이라고 가정하는 프로그래머로부터 사용자를 보호합니다.
Adam Liss

감사합니다 == true. 어쨌든 내 진짜 스타일에 더 가깝다. :)
Charles Beattie 2011 년

0

이것에 대한 나의 불만은 블록 범위를 잃는다는 것입니다. gotos 사이에 선언 된 모든 지역 변수는 루프가 끊어진 경우에도 유효합니다. (당신은 루프가 영원히 실행된다고 가정하고 있을지도 모르지만, 나는 그것이 원래의 작가가 묻는 질문이라고 생각하지 않습니다.)

일부 개체는 적절한 시간에 호출되는 dtor에 따라 달라질 수 있으므로 범위 지정 문제는 C ++에서 더 큰 문제입니다.

나에게 goto를 사용하는 가장 좋은 이유는 다단계 초기화 프로세스 중에 하나가 실패하면 모든 inits를 백 아웃하는 것이 중요합니다.

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;

0

나는 goto의 자신을 사용하지 않지만 특정 경우에 사용하는 사람과 한 번 작업했습니다. 내가 정확히 기억한다면, 그 근거는 성능 문제를 주변에 - 그는 또한 특정 규칙했다 방법을 . 항상 동일한 기능에 있으며 레이블은 항상 goto 문 아래에있었습니다.


4
추가 데이터 : 함수 외부로 이동하기 위해 goto를 사용할 수 없으며, C ++에서는 goto를 통해 객체 생성을 우회하는 것이 금지되어 있습니다
paercebal

0
#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}

-1

@Greg :

다음과 같이 예를 들어보십시오.

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}

13
불필요한 수준의 들여 쓰기를 추가하여 가능한 많은 실패 모드가있는 경우 실제로 털이 많아 질 수 있습니다.
Adam Rosenfield

6
나는 언제든지 goto에 들여 쓰기를 할 것입니다.
FlySwat

13
또한 코드 줄이 훨씬 더 많고 경우 중 하나에서 중복 함수 호출이 있으며 doD를 올바르게 추가하기가 훨씬 더 어렵습니다.
Patrick

6
나는 Patrick의 의견에 동의합니다. 저는이 관용구가 약 8 단계의 깊이로 중첩 된 여러 조건과 함께 사용되는 것을 보았습니다. 매우 불쾌합니다. 특히 믹스에 새로운 것을 추가하려고 할 때 오류가 발생하기 쉽습니다.
Michael Burr

11
이 코드는 "다음 주에 몇 주 안에 버그가 생길 것입니다"라고 외치면 누군가가 무언가를 취소하는 것을 잊을 것입니다. 모든 실행 취소가 동일한 위치에 필요합니다. 그것은 네이티브 OO 언어에서 "마침내"와 같습니다.
Ilya
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.