답변:
내가 알고있는 "goto"문을 사용하는 데는 몇 가지 이유가 있습니다 (일부는 이미 이에 대해 언급했습니다).
기능을 완전히 종료
종종 함수에서 자원을 할당하고 여러 곳에서 종료해야 할 수도 있습니다. 프로그래머는 함수의 끝에 리소스 정리 코드를 넣어 코드를 단순화 할 수 있으며 함수의 모든 "종료 지점"이 정리 레이블로 이동합니다. 이런 식으로 함수의 "종료 지점"마다 정리 코드를 작성할 필요가 없습니다.
중첩 루프 종료
중첩 루프에 있고 모든 루프 를 해제해야하는 경우 goto는 break 문 및 if-check보다 훨씬 깨끗하고 간단합니다.
저수준 성능 개선
이것은 per-critical 코드에서만 유효하지만 goto 문은 매우 빠르게 실행되며 함수를 이동할 때 부스트를 줄 수 있습니다. 그러나 컴파일러는 일반적으로 gotos가 포함 된 코드를 최적화 할 수 없기 때문에 양날의 검입니다.
이 모든 예에서 gotos는 단일 기능의 범위로 제한됩니다.
goto
하는 return
것은 어리석은 일입니다. goto
억압 된 환경 에서 자란 사람들 (즉, 우리 모두)이 도덕적으로 중요한 것을 사용하는 것에 대해 더 나은 느낌을 갖도록 하기 위해 아무것도 "리팩토링"하는 것이 아니라 단지 "이름을 바꾸는"것입니다 goto
. 나는 내가 그것을 사용 하는 루프를보고 싶어하고 조금만 보아라. goto
그 자체가 단지 도구 일 뿐이다 goto
.
break
, continue
, return
기본적으로 있습니다 goto
, 단지 좋은 포장이다.
do{....}while(0)
Java에서 작동한다는 사실을 제외하고는 goto보다 더 나은 아이디어가되는 방법을 보지 못했습니다 .
goto
Edsger Dijkstra의 GoTo 고려 된 유해한 기사는 직간접 적으로 반박하는 모든 사람이 자신의 입장을 입증합니다. 너무 나쁜 Dijkstra의 기사는 요즘 문이 사용되는 방식과 거의 관련goto
이 없으므로 기사의 내용은 현대 프로그래밍 장면에는 거의 적용되지 않습니다. 그만큼goto
없는 밈은 이제 종교, 즉 대제사장과 지각 된 이단자들의 떨림 (또는 더 나쁘게)에서 지시 된 경전까지 거슬러 올라갑니다.
Dijkstra의 논문을 맥락에 두어 주제에 대해 약간의 조명을 줍시다.
Dijkstra가 그의 논문을 썼을 때 당시 인기있는 언어는 BASIC, FORTRAN (이전 방언) 및 다양한 어셈블리 언어와 같은 구조화되지 않은 절차 적 언어였습니다. 고급 언어를 사용하는 사람들 은 "스파게티 코드"라는 용어를 일으킨 뒤틀리고 뒤틀린 실행 스레드로 코드베이스 전체 를 뛰어 넘는 것이 일반적이었습니다 . 마이크 메이필드 (Mike Mayfield)가 쓴 클래식 트렉 (Trek) 게임 에 뛰어 들어 어떻게 작동하는지 알아 내면이 사실을 알 수 있습니다. 잠시 살펴보고 살펴보십시오.
이건 다 익스트라는 1968 년 자신의 논문에 대해 난간되었다 "문에 이동의 억제되지 않은 사용"입니다 본 즉 그 논문을 쓰는 그를 주도 그가 살았던 환경입니다. 원하는 시점에서 코드에서 원하는 곳으로 이동할 수있는 능력은 그가 비판하고 멈추라는 요구였습니다. 이를 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()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
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
!
따라서 지금까지이 책을 읽고 자하는 사람들에게는 몇 가지 중요한 사항이 있습니다.
goto
문은 프로그래밍 환경을 위해 작성되었습니다 goto
했다 훨씬
더 가능성이 어셈블러없는 가장 현대적인 언어보다 손상.goto
이 때문에 모든 용도를 자동으로 버리는 것은 "한 번 재미를 보려고했지만 마음에 들지 않았으므로 지금은 반대한다"고 말하는 것이 합리적입니다.goto
다른 구문으로 적절하게 대체 할 수없는 코드에서 현대적인 (비유 기적) 구문을 합법적으로 사용합니다 .godo
" 대신에 항상 거짓 do
루프가 사용되지 않는 " "abomination 과 같은 현대적인 제어문도 불법적 으로 사용 break
됩니다 goto
. 이것들은 종종 신중하게 사용하는 것보다 더 나쁩니다 goto
.goto
모범 사례를 맹목적으로 따르는 것은 모범 사례가 아닙니다. goto
흐름 제어의 기본 형태 인 문장 을 피하는 아이디어는 읽을 수없는 스파게티 코드를 생성하지 않는 것입니다. 적절한 장소에서 드물게 사용하는 경우 아이디어를 표현하는 가장 단순하고 명확한 방법 일 수 있습니다. Zortech C ++ 컴파일러 및 D 프로그래밍 언어의 작성자 인 Walter Bright는이를 자주 사용하지만 신중하게 사용합니다. goto
그의 진술 에도 불구하고 그의 코드는 여전히 완벽하게 읽을 수 있습니다.
결론 : 피하기 goto
위해 피하는 goto
것은 의미가 없습니다. 실제로 피하고 싶은 것은 읽을 수없는 코드를 생성하는 것입니다. 귀하의 경우 goto
-laden 코드를 읽을 수있는, 다음에 아무것도 잘못이있다.
goto
프로그램 흐름 1 (일명“스파게티 코드”) 에 대한 추론을 하기 때문에 goto
일반적으로 누락 된 기능을 보상하는 데만 사용됩니다. goto
실제로 사용할 수는 있지만 언어가 더 체계적인 변형을 제공하지 않는 경우에만 사용할 수 있습니다. 같은 목표. 의심의 예를 보자.
우리가 사용하는 goto의 규칙은 goto가 함수의 단일 종료 정리 지점으로 건너 뛰는 것이 좋습니다.
이는 사실이지만 언어가 정리 코드 (예 : RAII 또는 finally
)를 작업을 위해 특별히 구축 된 경우와 동일) 또는 합당한 이유가없는 경우에만 가능합니다. 구조적 예외 처리를 사용합니다 (그러나 매우 낮은 수준을 제외 하고는이 경우가 없습니다).
대부분의 다른 언어에서 허용되는 유일한 사용은 goto
중첩 루프를 종료하는 것입니다. 심지어 외부 루프를 자체 방법으로 들어 올려 return
대신 사용하는 것이 거의 항상 좋습니다 .
그 외에는 goto
특정 코드에 대해 충분히 생각하지 못했다는 표시가 있습니다.
1goto
일부 제한 사항 (예 : goto
기능에 뛰어 들거나 벗어날 수 없음)을 구현 하도록 지원 하는 현대 언어 는 근본적으로 동일하게 유지됩니다.
또한 다른 언어 기능, 특히 예외는 마찬가지입니다. 예외가 아닌 프로그램 흐름을 제어하기 위해 예외를 사용하지 않는 규칙과 같이 표시된 경우에만 이러한 기능 만 사용하는 엄격한 규칙이 있습니다.
finally
? 따라서 오류 처리 이외의 예외를 사용하는 것이 좋지만 사용 goto
이 나쁩니 까? 나는 예외 가 상당히 적절 하다고 생각 합니다.
글쎄요, 항상 나쁜 것 하나가 있습니다 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
do{}while(false)
관용구로 생각할 수 있습니다. 당신은 동의 할 수 없습니다 : D
goto after_do_block;
실제로 말하지 않고 말하는 방법이라는 것을 알게 될 것입니다. 그렇지 않으면 ... 정확히 한 번 실행되는 "루프"? 제어 구조의 남용이라고 부릅니다.
#define
사용하는 것보다 s가 얼마나 많은지, 몇 배나 더 나쁜지 아주 잘 보여 goto
에서 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;
}
편집 : "경과 없음"규칙에는 한 가지 예외가 있습니다. 사례 설명에 코드가없는 경우 대체가 허용됩니다.
goto case 5:
사례 1 인 경우 말할 수 있기 때문에 ). Konrad Rudolph의 대답은 여기에 맞는 것 같습니다 : goto
누락 된 기능을 보상하고 있습니다 (실제 기능보다 덜 명확합니다). 우리가 실제로 원하는 것이 실패라면, 아마도 가장 좋은 디폴트는 넘어지지 않을 것이지만, continue
명시 적으로 요청하는 것입니다.
#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
할 수 있습니다. .)
나는 수년에 걸쳐 몇 줄 이상의 어셈블리 언어를 썼습니다. 궁극적으로 모든 고급 언어는 gotos로 컴파일됩니다. 좋아, "분기"나 "점프"또는 그 밖의 다른 것을 불러라. 누구나 고 토레스 어셈블러를 작성할 수 있습니까?
이제, 고토로 폭동을 일으키는 것은 스파게티 볼로냐의 요리법이라고 Fortran, C 또는 BASIC 프로그래머에게 지적 할 수 있습니다. 그러나 대답은 피하는 것이 아니라 신중하게 사용하는 것입니다.
칼은 음식을 준비하거나 누군가를 해방 시키거나 누군가를 죽이는 데 사용될 수 있습니다. 우리는 후자를 두려워하지 않고 칼을 사용하지 않습니까? 마찬가지로 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가 나쁜 이유 중 하나는 중첩 하지만 중첩되지 않은 루프 를 만드는 데 사용할 수 있기 때문입니다 .
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
이것은 (a, b, c, b, a, b, a, b, ...)와 같은 시퀀스가 가능한 기괴하지만 합법적 인 흐름 제어 구조를 만들어 컴파일러 해커를 불행하게 만듭니다. 분명히 이런 유형의 구조에 의존하지 않는 많은 영리한 최적화 트릭이 있습니다. (드래곤 북의 사본을 확인해야합니다 ...) 이것의 결과 (일부 컴파일러 사용)는 goto
s 가 포함 된 코드에 대해 다른 최적화가 수행되지 않을 수 있습니다 .
그냥 "아, 그건 그렇고", 컴파일러가 더 빠른 코드를 내도록 설득하는 것이 좋다는 것을 알고 있다면 유용 할 것이다 . 개인적으로, goto와 같은 트릭을 사용하기 전에 무엇이 가능하고 무엇이 아닌지에 대해 컴파일러에 설명하려고하지만 어쨌든 goto
어셈블러를 해킹하기 전에 시도 할 수도 있습니다 .
goto
입니다 유용 당신이 그렇지 않으면 논리적 뒤틀림의 무리를 필요로 같은 루프를 구성 할 수 있다는 것입니다. 또한 옵티마이 저가 이것을 다시 쓰는 방법을 모른다면 good 이라고 주장합니다 . 이와 같은 루프는 성능이나 가독성을 위해 수행해서는 안되지만 정확히 발생하는 순서이기 때문입니다. 어떤 경우에는 특히 옵티마이 저가 나사로 조이기를 원하지 않습니다 .
나는 어떤 사람들은 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
것은 보너스입니다.
container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();
입니다. 프로그래밍이 훨씬 읽기 쉽습니다. 이 경우 각 함수는 순수한 함수로 작성 될 수 있으며 추론하기가 훨씬 쉽습니다. 이제 주 함수는 함수 이름으로 코드의 기능을 설명한 다음 원하는 경우 해당 정의를보고 코드의 기능을 확인할 수 있습니다.
우리가 사용하는 goto의 규칙은 goto가 함수의 단일 종료 정리 지점으로 건너 뛰는 것이 좋습니다. 실제로 복잡한 기능에서는 다른 규칙으로 넘어갈 수 있도록이 규칙을 완화합니다. 두 경우 모두 오류 코드 검사와 함께 자주 발생하는 중첩 된 if 문을 피하여 가독성과 유지 관리에 도움이됩니다.
도널드 크 누스 (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는 인수의 모든 측면을 다루므로 누군가가 문제를 다시 해시하기 전에 읽어야합니다.
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
...
}
그렇다면 왜 그렇습니까?
C에는 다중 레벨 / 레이블 구분이 없으며 모든 제어 플로우를 C의 반복 및 의사 결정 기본 요소로 쉽게 모델링 할 수있는 것은 아닙니다. gotos는 이러한 결함을 해결하기 위해 먼 길을갑니다.
때때로 의사 멀티 레벨 브레이크의 종류에 영향을 미치기 위해 일종의 플래그 변수를 사용하는 것이 더 명확하지만 항상 goto보다 우월하지는 않습니다 (적어도 goto는 플래그 변수와 달리 제어가 어디로 가는지 쉽게 결정할 수 있습니다) ), 때로는 이동을 피하기 위해 플래그 / 기타 왜곡의 성능 가격을 지불하고 싶지 않습니다.
libavcodec는 성능에 민감한 코드입니다. 제어 흐름을 직접 표현하는 것이 우선 순위가 더 높을 수 있습니다.
do {} while (false) 사용법이 완전히 반란을 일으 킵니다. 이상한 경우가 필요하다는 것을 확신 할 수는 있지만 깨끗한 코드는 결코 아닙니다.
이러한 루프를 수행 해야하는 경우 플래그 변수에 대한 의존성을 명시 적으로 지정하지 않겠습니까?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
/*empty*/
할 stepfailed = 1
? 어쨌든 이것이 어떻게 A보다 낫 do{}while(0)
습니까? 둘 다, 당신 break
은 그것에서 (또는 당신의 stepfailed = 1; continue;
) 밖으로 해야합니다 . 나에게 불필요 해 보인다.
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 에디션과 달리)은 앞으로 진행되는 한 고 토스를 허용합니다.
어떤 이들은 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:;
Perl에서, 레이블을 사용하여 루프에서 "goto"- "last"문을 사용하십시오. 이는 break와 유사합니다.
이를 통해 중첩 루프를보다 효과적으로 제어 할 수 있습니다.
전통적인 goto 레이블 도 지원되지만 이것이 원하는 것을 달성하는 유일한 방법 인 서브 인스턴스와 루프가 충분하다는 것은 확실하지 않습니다. 대부분의 경우 서브 루틴과 루프가 충분해야합니다.
goto &subroutine
. 스택에서 현재 서브 루틴을 바꾸는 동안 서브 루틴을 현재 _로 시작합니다.
'goto'의 문제점과 'goto-less programming'운동의 가장 중요한 논점은 코드를 너무 자주 사용하면 코드가 올바르게 작동하더라도 읽을 수없고, 유지할 수없고, 확인할 수없는 등이된다는 것입니다. 'goto'사례는 스파게티 코드로 연결됩니다. 개인적으로, 나는 왜 'goto'를 사용할 것인지에 대한 정당한 이유를 생각할 수 없습니다.
goto
) 을 포함하는 비용을 측정하는 데 유효한 논거가 될 수 있습니다 . @cschol의 사용법은 비슷합니다. 현재 언어를 디자인하지 않았지만 기본적으로 디자이너의 노력을 평가하고 있습니다.
goto
하면 변수가 존재하는 상황을 제외하고는 누군가가 필요할 수있는 모든 종류의 제어 구조를 지원하는 것보다 저렴합니다. 코드를 작성 goto
하는 것은 다른 구조를 사용하는 것만 큼 좋지는 않지만 그러한 코드를 작성 goto
하는 것은 "표현의 허점"을 피하는 데 도움 이 됩니다. 언어가 효율적인 코드를 작성할 수없는 구조입니다.
goto
가 코드 검토 사이트에 포함 된 코드를 게시 할 때마다 goto
코드의 논리 가 크게 단순화됩니다.
물론 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 진술에 대한 사례" 에서 확인할 수 있습니다 .
다음과 같은 경우 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;
}
두 번째 버전은 할당 해제 명령문에서 무언가를 변경해야 할 때 (각각 코드에서 한 번 사용됨) 더 쉬워지고 새 분기를 추가 할 때 생략 할 가능성을 줄입니다. 할당 해제가 다른 "레벨"에서 수행 될 수 있기 때문에 함수로 이동하면 도움이되지 않습니다.
finally
C #에 블록 이있는 이유입니다
finally
). 대안으로 goto
s를 사용 하지만 항상 모든 정리 를 수행하는 공통 종료점 을 사용하십시오. 그러나 각 정리 방법은 null이거나 이미 깨끗한 값을 처리 할 수 있거나 조건부 테스트로 보호되므로 적절하지 않은 경우 건너 뜁니다.
goto
을 가진 동일한 종료점으로 가는 s 를 사용하는 대안을 참조하십시오 (이것은 리소스마다 if가 필요합니다). 그러나 C
코드를 C로 사용하는 이유가 무엇이든간에 가장 올바른 "직접적인"코드를 선호하는 트레이드 오프는 당신이 옳을 때 사용 하는 것이 좋습니다. (나의 제안은 주어진 자원이 할당되거나 할당되지 않은 복잡한 상황을 처리합니다. 그러나이 경우에는 과잉입니다.)
문자별로 문자열을 처리 할 때 유용합니다.
이 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를 신중하게 사용했다. 당신은 그것들을 대부분 필요로하지는 않지만, 그런 이상한 경우에는 좋은 것입니다.