“goto”진술로 어떤 종류의 버그가 발생합니까? 역사적으로 중요한 예가 있습니까? “Neal Stephenson은 자신의

루프에 중첩 된 루프를 없애기 위해 저장한다는 것을 이해합니다. 이 goto문장은 사용되지 않는 버그가 발생하기 쉬운 프로그래밍 스타일로 회피되고 비난됩니다.

XKCD
대체 텍스트 : “Neal Stephenson은 자신의 레이블 이름을 ‘dengo’라고 생각하는 것이 귀엽다고 생각합니다 .
: http://xkcd.com/292/

내가 이것을 일찍 배웠기 때문에; goto실제로 어떤 유형의 버그가 실제로 발생하는지에 대한 통찰력이나 경험이 없습니다 . 그래서 우리는 여기서 무엇을 이야기하고 있습니까?

  • 불안정?
  • 유지할 수 없거나 읽을 수없는 코드?
  • 보안 취약점?
  • 완전히 다른 것?

“goto”문장은 실제로 어떤 종류의 버그를 유발합니까? 역사적으로 중요한 예가 있습니까?



답변

다른 답변에서 아직 보지 못한 것을 볼 수있는 방법이 있습니다.

범위에 관한 것입니다. 좋은 프로그래밍 실습의 주요 기둥 중 하나는 스코프를 엄격하게 유지하는 것입니다. 단지 몇 가지 논리적 단계 이상을 감독하고 이해하는 정신적 능력이 부족하기 때문에 범위가 좁습니다. 따라서 작은 블록 (각각 하나가 “일”이 됨)을 만든 다음 하나가되는 더 큰 블록을 만드는 블록을 만듭니다. 이것은 일을 관리하고 이해하기 쉽게합니다.

goto는 논리의 범위를 전체 프로그램으로 효과적으로 확장시킵니다. 이것은 단지 몇 줄에 걸친 가장 작은 프로그램을 제외하고는 당신의 두뇌를 물리 칠 것입니다.

그래서 당신은 당신이 불쌍한 작은 머리를 조사하고 점검하기에 너무 많은 것이 있기 때문에 더 이상 실수를했는지 알 수 없습니다. 이것은 실제 문제이며 버그는 단지 결과 일뿐입니다.


답변

goto그 자체가 나쁘지 는 않습니다 . 결국 컴퓨터의 모든 점프 명령은 고토 (goto)입니다. 문제는 “프로그래밍 된 프로그래밍 (flow-chart)”프로그래밍이라고 할 수있는 구조화 된 프로그래밍보다 인간적인 프로그래밍 스타일이 있다는 것입니다.

플로우 차트 프로그래밍 (내 세대 사람들이 배우고 아폴로 달 프로그램에 사용)에서, 문장 실행을위한 블록과 의사 결정을위한 다이아몬드가있는 다이어그램을 만들고, 그 라인을 모든 곳의 라인으로 연결할 수 있습니다. (소위 “스파게티 코드”)

스파게티 코드의 문제점은 프로그래머가 자신이 옳은 것을 “알 수”있지만 어떻게 자신이나 다른 사람에게 그것을 증명할 수 있는가하는 것입니다. 실제로, 잠재적 인 오작동이있을 수 있으며 항상 정확하다는 지식은 틀릴 수 있습니다.

함께 온 구조화 프로그래밍 을 시작 엔드 블록을위한 반면, IF-다른, 등등. 이것들은 여전히 ​​어떤 일을 할 수 있다는 이점이 있었지만, 조심스럽게 조심한다면 코드가 올바른지 확인할 수 있습니다.

물론 사람들은 없이도 스파게티 코드를 작성할 수 있습니다 goto. 일반적인 방법은 while(...) switch( iState ){...다른 경우 iState를 다른 값으로 설정하는를 작성하는 것 입니다. 실제로 C 또는 C ++에서는 매크로를 작성하여 이름을 GOTO지정할 수 있으므로 사용하지 않는다고 말하는 goto것은 차이가없는 구별입니다.

코드 검증이 무제한을 배제 할 수있는 방법의 예로써 goto, 오래 전에 나는 동적으로 변화하는 사용자 인터페이스에 유용한 제어 구조를 우연히 발견했다. 나는 그것을 차등 실행 이라고 불렀다 . 그것은 완전히 튜링 보편적이지만, 그 정확성 증거는 순수한 구조적 프로그래밍에 따라 달라집니다 – 아니 goto, return, continue, break, 또는 예외.


답변

왜 고토가 위험한가요?

  • goto자체적으로 불안정성을 유발하지 않습니다. 약 10 만 goto 에도 Linux 커널 은 여전히 ​​안정성 모델입니다.
  • goto그 자체만으로는 보안 취약점이 발생하지 않아야합니다. 그러나 일부 언어에서는 예외 관리 블록 과 함께 try/ catch예외 관리 블록을 혼합하면 이 CERT 권장 사항에 설명 된대로 취약점이 발생할 수 있습니다 . 주류 C ++ 컴파일러는 이러한 오류를 표시하고 방지하지만 불행히도 이전 또는 더 이국적인 컴파일러는 그렇지 않습니다.
  • goto읽을 수없고 유지할 수없는 코드를 발생시킵니다. 이것은 스파게티 코드 라고도 합니다 . 왜냐하면 스파게티 플레이트 에서처럼 너무 많은 고토가있을 때 제어 흐름을 따르는 것이 매우 어렵 기 때문입니다.

스파게티 코드를 피하고 고토를 몇 개만 사용하더라도 버그와 리소스 누출을 여전히 촉진합니다.

  • 명확한 중첩 블록과 루프 또는 스위치와 함께 구조 프로그래밍을 사용하는 코드는 따르기 쉽습니다. 제어 흐름은 매우 예측 가능합니다. 따라서 불변량을 존중하는 것이 더 쉽습니다.
  • A의 goto문, 당신은 간단 흐름을 중단하고 기대를 휴식. 예를 들어, 여전히 자원을 비워야한다는 것을 알지 못할 수 있습니다.
  • goto다른 장소의 많은 사람들 이 단일 goto 대상으로 보낼 수 있습니다. 따라서이 장소에 도달했을 때의 상태를 확실하게 알 수 없습니다. 그러므로 틀리거나 근거가없는 가정을 할 위험은 상당히 크다.

추가 정보 및 인용문 :

C는 무한히 어설픈 goto문장과 레이블을 제공합니다 . 공식적으로 goto는 결코 필요하지 않으며 실제로는 코드없이 코드를 작성하는 것이 거의 항상 쉽습니다. (…)
그럼에도 불구하고 우리는 고토의이 장소를 찾을 수있는 몇 가지 상황을 제안합니다. 가장 일반적인 용도는 한 번에 두 개의 루프를 끊는 것과 같이 일부 중첩 된 구조에서 처리를 포기하는 것입니다. (…)
우리는 그 문제에 대해 독단적이지는 않지만, goto 선언문을 조금만 사용해야 하는 것 같습니다 .

  • James Gosling & Henry McGilton은 1995 년 자바 환경 백서 에서 다음과 같이 썼습니다 .

    더 이상 Goto 문
    이 없습니다. Java에는 goto 문이 없습니다. 연구 결과에 따르면 goto는 단순히 “있어서”있기보다는 자주 사용됩니다. goto를 제거하면 언어가 간단 해졌습니다 (…) C 코드의 약 100,000 줄에 대한 연구에 따르면 goto 문의 대략 90 %가 순전히 중첩 루프에서 벗어나는 효과를 얻는 데 사용 된 것으로 나타났습니다. 위에서 언급했듯이, 다단계 나누기와 계속해서 goto 문의 필요성을 대부분 제거합니다.

  • Bjarne Stroustrup 은 다음과 같은 매력적인 용어로 용어집 에서 goto를 정의합니다 .

    goto-악명 높은 goto. 기계 생성 C ++ 코드에 주로 유용합니다.

언제 갈 수 있나요?

K & R처럼 저는 고 토스에 대해 독단적이지 않습니다. 나는 goto가 인생을 편하게 할 수있는 상황이 있음을 인정한다.

일반적으로 C에서 goto는 다중 레벨 루프 종료 또는 지금까지 할당 된 모든 자원을 해제 / 해제하는 적절한 종료점에 도달해야하는 오류 처리를 허용합니다 (예 : 순서대로 다중 할당은 다중 레이블을 의미 함). 이 기사 는 Linux 커널에서 goto의 다른 용도를 정량화합니다.

개인적으로 나는 그것을 피하고 10 년 동안 최대 10 개의 고토를 사용했습니다. 나는 if더 읽기 쉽다고 생각하는 중첩 된을 사용하는 것을 선호합니다 . 이것이 너무 깊은 중첩으로 이어질 때 함수를 작은 부분으로 분해하거나 부울 표시기를 계단식으로 사용하도록 선택했습니다. 오늘날의 최적화 컴파일러는와 동일한 코드와 거의 동일한 코드를 생성 할만큼 영리 goto합니다.

goto의 사용은 언어에 따라 크게 다릅니다.

  • C ++에서 RAII를 올바르게 사용 하면 컴파일러가 범위를 벗어난 객체를 자동으로 파괴하므로 리소스 / 잠금이 정리되어 더 이상 갈 필요가 없습니다.

  • 자바에서는 더 고토의 필요성 (자바의 위 저자의 인용이 볼 수 없다 우수한 스택 오버플로 대답 ) : 엉망이, 청소 가비지 컬렉터 break, continuetry/ catch예외 처리는 모든 케이스 커버 goto도움이 될 수있는, 그러나으로는 더 안전하고 더 나은 방법. Java의 인기는 현대 언어로 goto 문을 피할 수 있음을 증명합니다.

유명한 SSL goto fail 취약점 확대

중요 면책 조항 : 의견의 격렬한 논의를 고려할 때 goto 문 이이 버그의 유일한 원인이라고 주장하지 않는다는 것을 분명히하고 싶습니다. 나는 goto가 없으면 버그가 없다고 가정하지 않습니다. 고토가 심각한 버그에 관여 할 수 있음을 보여주고 싶습니다.

나는 goto프로그래밍의 역사에서 몇 가지 심각한 버그가 관련되어 있는지 잘 모른다 . 그러나 iOS의 보안을 약화시킨 유명한 Apple SSL 버그 가있었습니다. 이 버그를 일으킨 goto진술 은 잘못된 진술이었습니다.

어떤 사람들은 버그의 근본 원인 자체가 goto 문이 아니라 잘못된 복사 / 붙여 넣기, 잘못된 들여 쓰기, 조건부 블록 주위에 중괄호 누락 또는 개발자의 작업 습관이라고 주장합니다. 나는 그것들 중 어느 것도 확인할 수 없다.이 모든 주장들은 가능한 가설과 해석 일 것이다. 아무도 모른다. ( 한편, 누군가가 의견에서 제안한대로 잘못 된 합병의 가설은 동일한 기능의 다른 들여 쓰기 불일치를 고려할 때 매우 좋은 후보로 보입니다 .)

유일한 객관적인 사실은 복제 goto가 기능을 조기에 종료한다는 것입니다. 코드를 살펴보면 동일한 결과를 초래할 수있는 유일한 단일 명령문이 리턴 일 것입니다.

이 오류는 기능에 SSLEncodeSignedServerKeyExchange()에서 이 파일 :

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

실제로 조건부 블록을 둘러싼 중괄호는 버그를 방지 할 수있었습니다.
컴파일시 구문 오류 (따라서 수정) 또는 중복 무해한 이동으로 이어질 수 있습니다. 그건 그렇고, GCC 6은 일관성없는 들여 쓰기를 감지하는 옵션 경고 덕분에 이러한 오류를 발견 할 수 있습니다.

그러나 처음에는 더 구조화 된 코드로 이러한 모든 문제를 피할 수있었습니다. 따라서 goto는 최소한이 버그의 원인입니다. 그것을 피할 수있는 최소한 두 가지 방법이 있습니다.

접근법 1 : if 절 또는 중첩 ifs

오류에 대한 많은 조건을 순차적으로 테스트하는 대신 fail문제가 발생한 경우 레이블로 보낼 때마다 if잘못된 전제 조건이없는 경우에만 암호화 작업을 수행하도록 선택할 수 있습니다 .

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

접근 방식 2 : 오류 누산기 사용

이 접근법은 여기의 거의 모든 명령문이 err오류 코드 를 설정하는 함수를 호출하고 err0 인 경우에만 나머지 코드를 실행 한다는 사실을 기반으로합니다 (즉, 오류없이 실행 된 함수). 안전하고 읽기 쉬운 좋은 대안은 다음과 같습니다.

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

여기에는 단일 goto가 없습니다. 실패 종료 지점으로 빠르게 이동할 위험이 없습니다. 그리고 시각적으로 잘못 정렬 된 선이나 잊혀진 부분을 쉽게 발견 할 수 있습니다 ok &&.

이 구조는 더 간결합니다. C에서 논리의 두 번째 부분과 ( &&)은 첫 번째 부분이 참인 경우에만 평가 된다는 사실에 근거 합니다. 실제로 최적화 컴파일러에 의해 생성 된 어셈블러는 gotos가 포함 된 원래 코드와 거의 동일합니다. 옵티마이 저는 조건 체인을 매우 잘 감지하고 코드를 생성합니다. 처음에는 null이 아닌 반환 값이 끝으로 이동합니다 ( online proof ).

테스트 단계에서 ok 플래그와 오류 코드 사이의 불일치를 식별 할 수있는 기능 종료시 일관성 검사를 예상 할 수도 있습니다.

assert( (ok==false && err!=0) || (ok==true && err==0) );

실수 ==0!=0또는 논리적 커넥터 오류 로 대체 된 실수 는 디버깅 단계에서 쉽게 발견됩니다.

말했듯이 : 나는 대체 구문이 버그를 피했다고 가정하지 않습니다. 나는 그들이 버그를 더 어렵게 만들 수 있다고 말하고 싶습니다.


답변

유명한 Dijkstra 기사는 일부 프로그래밍 언어가 실제로 여러 개의 진입 점과 출구 점을 갖는 서브 루틴을 작성할 수있는 시점에 작성되었습니다 . 다시 말해서, 실제로 함수의 중간으로 뛰어 들어가서 실제로 함수를 호출하거나 일반적인 방식으로 함수를 반환하지 않고 함수 내의 어느 곳 으로든 뛰어 넘을 수 있습니다. 그것은 여전히 ​​어셈블리 언어에 해당됩니다. 아무도 그러한 접근 방식이 현재 우리가 사용하는 소프트웨어 작성 방식보다 우수하다고 주장하는 사람은 없습니다.

대부분의 현대 프로그래밍 언어에서 함수는 하나의 진입 점과 하나의 출구 점 으로 매우 구체적으로 정의됩니다 . 진입 점은 함수에 매개 변수를 지정하고 호출하는 지점이며, 종료점은 결과 값을 리턴하고 원래 함수 호출 다음의 명령에서 실행을 계속하는 지점입니다.

그 기능 내에서, 당신은 이유 내에서 원하는 것을 할 수 있어야합니다. 기능에 goto 또는 두 개를 넣으면 속도가 명확하거나 향상되면 왜 안됩니까? 함수의 요점은 명확하게 정의 된 약간의 기능을 격리하여 더 이상 내부적으로 작동하는 방식에 대해 생각할 필요가 없다는 것입니다. 일단 작성되면 사용하면됩니다.

그리고 네, 함수 내에 여러 개의 return 문을 가질 수 있습니다. 올바른 함수에는 항상 하나의 위치가 있습니다 (기본적으로 함수의 뒷면). 제대로 돌아갈 기회를 갖기 전에 goto로 함수에서 뛰어 내리는 것과 전혀 다릅니다.

따라서 실제로 goto를 사용하는 것이 아닙니다. 학대를 피하는 것입니다. 누구나 gotos를 사용하여 몹시 복잡한 프로그램을 만들 수 있다는 데 동의하지만, 기능을 남용하여 동일한 작업을 수행 할 수도 있습니다 (gosto를 남용하는 것이 훨씬 쉽습니다).

가치있는 것을 위해, 줄 번호 스타일의 BASIC 프로그램을 파스칼 및 중괄호 언어를 사용한 구조적 프로그래밍에 이르기까지 졸업 한 이래로 고토를 사용할 필요가 없었습니다. 내가 하나를 사용하고 싶은 유일한 시간은 중첩 루프에서 조기 종료를하는 것입니다 (루프에서 다중 레벨 초기 종료를 지원하지 않는 언어로)하지만 일반적으로 더 깨끗한 다른 방법을 찾을 수 있습니다.


답변

“goto”진술로 어떤 종류의 버그가 발생합니까? 역사적으로 중요한 예가 있습니까?

나는 gotofor 및 while 루프를 얻는 간단한 방법으로 자식으로 BASIC 프로그램을 작성할 때 문장 을 사용했습니다 (Commodore 64 BASIC에는 while 루프가 없었으며 for 루프의 올바른 구문과 사용법을 배우기에는 너무 미숙했습니다 ). 내 코드는 종종 사소한 것이었지만 모든 루프 버그는 즉시 goto 사용으로 인한 것일 수 있습니다.

저는 이제 고토 (goto)가 필요 없다고 판단한 고급 프로그래밍 언어 인 Python을 주로 사용합니다.

Edsger Dijkstra가 1968 년에 “Goto가 해로운 것으로 간주된다”고 선언했을 때, 관련 버그가 고토에서 비난받을 수있는 몇 가지 예를 제시하지 않았으며, 이는 더 높은 수준의 언어goto 에는 불필요하며 무엇을 위해 피해야한다고 선언했습니다 우리는 오늘날 일반적인 제어 흐름 인 루프와 조건을 고려합니다. 그의 말 :

이동을 제한없이 사용 하면 프로세스 진행 상황을 설명 할 의미있는 좌표 세트를 찾기가 매우 어려워집니다.
[…] 로 이동 약자로 문은 너무 원시적이다; 프로그램을 엉망으로 만드는 것은 너무 많은 초대입니다.

그는 코드를 디버깅 할 때마다 버그의 예를 산으로 만들었 goto습니다. 그러나 그의 논문은 goto고급 언어에는 불필요 하다는 증거로 뒷받침되는 일반화 된 입장 진술 이었습니다. 일반적인 버그는 문제의 코드를 정적으로 분석하는 능력이 없을 수 있다는 것입니다.

코드는 goto명령문 으로 정적 분석하기가 훨씬 어렵습니다 . 특히 제어 흐름 (이전에 사용했던)으로 돌아가거나 관련이없는 코드 섹션으로 이동하면 더욱 그렇습니다. 코드는 이런 식으로 작성 될 수있었습니다. 매우 부족한 컴퓨팅 리소스와 시스템의 아키텍처에 맞게 고도로 최적화되었습니다.

외경의 예

관리자가 매우 우아하게 최적화되었지만 코드의 특성으로 인해 “수정” 할 수없는 블랙 잭 프로그램이있었습니다 . 그것은 gotos에 크게 의존하는 머신 코드로 프로그래밍 되었으므로이 이야기는 매우 관련이 있다고 생각합니다. 이것은 내가 생각할 수있는 가장 좋은 정식 예입니다.

반대의 예

그러나 CPython의 C 소스 (가장 일반적이고 참조적인 Python 구현)는 goto명령문을 사용 하여 큰 영향을줍니다. 함수 내부의 관련없는 제어 흐름을 우회하여 함수의 끝 부분에 도달하여 가독성을 잃지 않고 기능을보다 효율적으로 만드는 데 사용됩니다. 이것은 구조적 프로그래밍 의 이상 중 하나를 존중 합니다. 함수에 대한 단일 종료 점이 있습니다.

기록을 위해,이 반례는 정적으로 분석하기가 매우 쉽다는 것을 알았습니다.


답변

wigwam 이 현재 건축 예술이었을 때 , 그들의 건축업자는 의심 할 여지없이 가발의 구성, 연기 탈출 방법 등에 관한 실질적인 조언을 줄 수있었습니다. 다행히 오늘날의 빌더는 아마도 그 조언을 대부분 잊어 버릴 수 있습니다.

역마차 가 현재의 교통 기술이었을 때, 운전자는 역마차 말, 고속도로 직원 을 방어하는 방법 등에 관해 실질적인 조언을 할 수있을 것입니다. 운 좋게도 오늘날 운전자는 그 조언을 대부분 잊어 버릴 수 있습니다.

펀치 카드 가 현재의 프로그래밍 기술이었을 때 , 실무자들은 마찬가지로 카드 구성, 문장 번호 매기기 등에 관한 실질적인 조언을 제공 할 수 있습니다. 나는 그 조언이 오늘날 매우 관련이 있는지 확실하지 않습니다.

당신은 “to statement to statement” 라는 문구를 알기에 충분히 나이가 있습니까? 당신은 그것을 알 필요는 없지만, 당신이 모르는 경우, 경고에 대한 경고 goto가 주로 관련된 역사적 맥락에 익숙하지 않습니다 .

오늘의 경고 goto는 그다지 중요하지 않습니다. 동안의 기본 교육과 함께 / 루프와 함수 호출을 위해, 당신도하지 않을 생각 를 _ 행 goto자주. 생각할 때 이유가있을 수 있으므로 계속 진행하십시오.

그러나 그 goto진술은 남용 될 수 없는가?

답 : 물론 악용 될 수는 있지만 상수가 제공 될 변수를 사용하거나 잘라 내기 및 붙여 넣기 프로그래밍과 같은 훨씬 일반적인 실수와 비교할 때 소프트웨어 공학에서 악용은 매우 작은 문제입니다. 리팩토링 무시로 알려진). 나는 당신이 많은 위험에 처해 있는지 의심합니다. longjmp원거리 코드로 제어를 사용 하거나 달리 전송 하지 않는 한 , 사용 goto하려고 생각 하거나 재미를 위해 그것을 시도하고 싶다면 계속하십시오. 너는 괜찮아 질거다.

당신은 악당 을 연기하는 최근의 공포 이야기가 부족하다는 것을 알 수 있습니다 goto. 이 이야기의 대부분 또는 전부는 30 세 또는 40 세인 것 같습니다. 그 이야기를 대부분 쓸모없는 것으로 여기면 당신은 확고한 입장에 서 있습니다.


답변

goto진술 을 통해 다른 훌륭한 답변에 한 가지를 추가하려면 프로그램의 특정 위치에 어떻게 도달했는지 정확하게 말하기가 어려울 수 있습니다. 특정 라인에서 예외가 발생했음을 알 수 있지만 goto코드에가 있으면 전체 프로그램을 검색하지 않고 상태를 유발하는 예외를 발생시키기 위해 실행 된 명령문을 알 수있는 방법이 없습니다. 호출 스택 및 시각적 흐름이 없습니다. 1000 줄 떨어진 명령문이있을 수 있습니다. 잘못된 상태로 실행 goto하면 예외를 일으킨 라인에 처할 수 있습니다.