mir.pe (일반/어두운 화면)
최근 수정 시각 : 2024-07-08 15:02:45

GOTO

고투에서 넘어옴

파일:나무위키+유도.png  
은(는) 여기로 연결됩니다.
옛 이름이 고투닷컴이였던 검색 광고 업체에 대한 내용은 오버추어 문서
번 문단을
부분을
, 월드 오브 워크래프트: 어둠땅의 인스턴스 던전인 고통의 투기장에 대한 내용은 고통의 투기장 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
, 에 대한 내용은 문서
번 문단을
번 문단을
부분을
부분을
참고하십시오.
1. 개요2. 특징3. 예제4. 단점5. 장점6. 기타

1. 개요

프로그래밍에서 어느 특정 줄 번호나 레이블로 건너뛰거나 돌아갈 때 쓰는 명령이다. 프로그램의 흐름을 바꾸는 가장 기본적인 명령으로, 일부 고급 언어에서 공통적으로 사용되는 명령이다.

CPU의 명령어 중에는 JUMP라는 것이 있는데 이를 이용하면 코드의 특정 부분으로 바로 이동할 수 있다. 이것을 이용하면 조건분기나 반복 등을 구현할 수 있어서 어셈블리어를 비롯한 저급 언어에서는 굉장히 자주 사용되는데, 이것을 고급 언어에서 유사하게 사용할 수 있도록 구현한 것이 바로 GOTO다.

제대로 사용하면 상당히 유용하게 쓸 수도 있으나 에츠허르 다익스트라의 영향으로 현대의 프로그래머들 사이에서는 사용이 꺼려지고 있으며, 아예 가르치지를 않다보니 존재조차 모르는 사람이 있을 정도로 사장된 명령이기도 하다. 그 이유는 아래 상세와 단점 항목 참조.

2. 특징

GOTO가 없는 언어도 많이 찾아볼 수 있는데, 몇 가지 이유가 있어서 그렇다.

일단 프로그래밍 언어 중 명령의 실행 순서를 중요하게 여기지 않는 언어들, 대표적으로 순수 함수형 언어( Haskell 등)와 일부 논리 프로그래밍 언어들(대표적으로 SQL)은 GOTO가 없다. 또한 일부 특수 목적 프로그래밍 언어 중에서 과거 시점으로 되돌아가는 게 불가능한 언어들( VHDL 등)이 있는데 이들 언어들에도 GOTO가 없다. 이들 언어들에는 앞으로도 영원히 GOTO가 추가될 일이 없는데, 언어의 서술 논리 자체에 '시간'이라는 개념이 없기 때문이다.

명령의 실행 순서가 중요하지만 GOTO를 지원하지 않는 경우도 있다. (대표적으로 Java의 경우 예약어로서는 존재하지만 기능이 없다.) 대부분의 프로그래밍 언어는 조건문과 반복문을 지원해주는데, 이를 이용하면 GOTO가 없어도 프로그램을 작성하는 데에 문제가 거의 없다. 게다가 아래에서 서술할 문제점 때문에 잘 지원하지 않는다.

참고로 GOTO는 동일 함수 내에서의 점프만 가능하다. 함수라는 개념이 없는 언어(어셈블리어, GW-BASIC 등)는 소스 코드 아무 곳으로나 점프가 가능하지만 함수 개념이 있는 언어에서는 함수 바깥의 레이블로 GOTO를 하려고 하면 컴파일 에러가 난다. 물론 이 함수도 뛰어넘어서 특정 코드로 이동하는 것이 아주 불가능한 것은 아니다. 예를 들어서 C/C++의 경우 setjmp, longjmp라는 함수를 제공하는데, 이것을 이용하면 함수를 넘어서 점프도 가능하다. 다만 이 함수가 하는 기능은 '현재의 스택의 상태와 코드의 위치'를 저장하여 특정 경우에 그 위치로 복귀하는 것이라서, 아직 setjmp를 만나지 않았거나 setjmp가 끝난 경우에는 점프할 수 없다. 무슨 말인지 이해가 잘 안 갈수도 있는데, C++의 try, catch, throw가 하는 역할과 비슷하다고 생각하면 된다.

3. 예제

역사상 극초창기 고급 프로그래밍 언어인 Mark I Autocode에서의 사용법 예제를 보자
1. STORE N, 1 # N에 1 저장 (초기값 설정)
2. STORE SUM, 0 # SUM에 0 저장 (합계 초기값)
3. ADD SUM, N # SUM에 N을 더함
4. STORE SUM # 결과를 SUM에 저장
5. ADD N, 1 # N에 1을 더하여 다음 숫자 생성
6. STORE N # 결과를 N에 저장
7. IF N > 10, END # N이 10보다 크면 프로그램 종료
8. GOTO 3 # 3번 명령으로 이동하여 루프 반복
9. LABEL END # 종료 지점
10. PRINT SUM # 합계 출력
이 코드는 1부터 10까지의 수를 더해 출력하는데, N이 10 초과될때까지 수를 1씩 증가시킨 뒤 더하는, 반복문 수행의 역할을 하고 있음을 알 수 있다.

BASIC에서의 사용 예를 들면 다음과 같다.

#!syntax basic
10 LET a = 10
20 PRINT "타잔이 " + a + "원짜리 팬티를 입고, " + (a + 10) + "원짜리 칼을 차고 노래를 한다. 아아아~"
30 LET a = a + 10
40 IF a < 100 THEN GOTO 20
50 END


이를 실행하면 10, 20, 30, 40, 20, 30, 40, 20, 30, 40, ..., 40, 50 순으로 실행하면서 타잔 노래를 100원까지 부른다. 이 goto문은 보면 알다시피, 반복문이라는 문법이 실질적으로 없던 시절 조건문과 함께 쓰여 사실상의 반복문 역할을 했다. 직접 돌려보고 싶으면 http://www.quitebasic.com/ 에 저 코드를 복붙해서 실행해보자.

배치 파일을 활용한 또 다른 예제로는
#!syntax powershell
@echo off
:12
cls
echo 예제입니다
set /p 암호=암호 입력:
if %암호%==22 goto 23
goto 12

:23
cls
echo 성공!
pause
exit

4. 단점

GOTO Statement Considered Harmful[1] - Edsger W. Dijkstra
고급 언어에서 GOTO문은 많은 커뮤니티에서 터부시되는 구문이다. 그 이유는 이게 너무 많이 쓰다 보면 구조가 무너지고 코드가 황폐화되면서 어째 실행은 잘 되긴 되는데 코드를 짠 프로그래머 자신도 헷갈려 하는 소리가 절로 나오며 읽고 유지보수하기가 힘들어지는 지경에 이르게 되기 때문이다. 이러한 상태로 된 코드를 ' 스파게티 코드'라고 한다. 이런 스파게티 스타일을 반대하고 구조적 프로그래밍을 주창한 네덜란드의 컴퓨터 과학자 에츠허르 다익스트라(Edsger Wybe Dijkstra)는 GOTO문의 사용이 프로그램의 정확성 분석과 증명 등의 측면에 해가 된다고 한 바 있다. 실제 프로그래밍할 때도 GOTO문을 남용하면 디버깅 불가+기능 추가 난해+다른 사람의 욕 처먹기+ 내가 이러려고 이 코드를 짰나 자괴감 들고 괴로운 사단 콤보가 일어날 수 있다. 또한 C언어에서 예외처리를 하기 위해 어쩔 수 없이 사용하는 경우도 있는데, setjmp, longjmp 같은 대용품이 존재한다.

이렇게 소스가 꼬이는 것은 사람에게만 문제가 되는 것이 아니다. 프로그램을 컴파일하면 최적화를 해야하는데, 언어 상에서 제공해주는 조건문/반복문 등은 자주 사용되거나 분기되는 특정 부분이 명확하기 때문에 최적화가 그리 어렵지는 않은 편이다. 하지만 GOTO를 사용할 경우 반복되는 부분이 명확해지지 않아서 최적화 결과가 좋지 않거나 시간이 오래 걸릴 수 있다.

5. 장점

#!syntax cpp
int sock_create_lite(int family, int type, int protocol, struct socket **res)
{

        err = security_socket_create(family, type, protocol, 1);
	if (err)
		goto out;

	sock = sock_alloc();
	if (!sock) {
		err = -ENOMEM;
		goto out;
	}

	sock->type = type;
	err = security_socket_post_create(sock, family, type, protocol, 1);
	if (err)
		goto out_release;
out:
	*res = sock;
	return err;
out_release:
	sock_release(sock);
	sock = NULL;
	goto out;
}

[2]

GOTO문의 남용은 소스코드의 이해를 어렵게 만들지만, 적절한 사용은 오히려 소스코드의 가독성과 명료성을 높이는 경우가 있다. 가령 다중 반복문에서의 탈출, 에러에 대한 예외 처리[3] 등 일부 작업에 한해서는 GOTO문을 사용하는 경우가 더 명료한 경우도 있다.

대표적인 예시로 리눅스 커널 소스코드를 까봐도 심심찮게 GOTO문을 발견할 수 있다.[4] 안정성이 매우 중요한 운영체제 커널에서 사용하는 것이다. 위의 코드도 리눅스 커널의 코드다. 터부시 되기에는 나름대로 쓰임새가 있는 것도 사실. 그러나 초보자에게는 많은 경우에 일종의 금기로써 가르친다. GOTO문이 유용한 경우는 어디까지나 특수한 케이스이고 잘못 사용했을 경우의 어디서부터 손대야할지 모르는 스파게티를 만들어버리는데다가 초보자의 경우 특히 가능성이 높기 때문. C언어보다 고수준 언어를 사용한다면 예외처리는 exception(try-catch)으로, C++같이 리소스 해제가 수동이지만 RAII를 지원하는 경우는 RAII를 사용하자. 용법은 같으면서 가독성은 증가한다. 다만 그런거 없는 C언어에서는 예외처리와 동적 리소스 해체에 goto를 주로 사용한다.[5]

이 GOTO문은 어셈블리어의 산물이다보니 어셈블리어에서는 이러한 기능의 사용이 강제된다. 어셈블리어에서는 프로그램의 흐름을 제어할 수 있는 구문이 JUMP[6] 계열의 명령어밖에 없는데, 이는 무조건적으로 혹은 특정 조건을 만족하면 명령어에서 지시하는 프로그램 카운터로 이동하는 구문이다. 고급 언어로 쓰인 프로그램도 디버거를 이용해 어셈블리 소스를 열어보면 전부 JUMP 인스트럭션으로 반복문이 구현되어있다. 그래서 어셈블리 프로그래밍을 가르치는 교재에서는 일부러 반복문을 GOTO 문법으로 고쳐서 쓰는 연습을 시키기도 하는데, 이렇게 하면 C로 작성한 코드와 어셈블리 코드가 거의 1:1 대응이 될 정도로 흡사해지기 때문이다.

다중 루프문의 몇 단계를 한꺼번에 탈출하는 데도 사용할 수 있지만, 남용할 경우 문제가 있다보니 Kotlin과 같은 경우 마냥 GOTO문을 그대로 구현하는 것이 아니라 GOTO문이 필요가 없도록 언어 내에서 바깥쪽의 루프를 탈출할 수 있는 방법을 마련해둔다.

6. 기타



[1] 이 GOTO문의 사용이 해가 된다고 지적한 서한의 제목이 'Go to Statement Considered Harmful'(GOTO문의 해로움)이였는데, 이 문서가 워낙에 유명하다보니 '... Considered Harmful'라는 어구도 인기를 끌어서 컴퓨터 과학 계열에서 어떤 것에 대한 비판을 할때 자주 쓰이는 어구가 되었다. 참고로 위 서한에 대한 비평으로 ' 'GOTO Statement Considered Harmful' Considered Harmful'('GOTO문의 해로움'의 해로움)이 있었고, 다시 이것에 대한 반응으로 도널드 무어 등은 ' 'GOTO Statement Considered Harmful' Considered Harmful' Considered Harmful? (' 'GOTO문의 해로움'의 해로움'의 해로움?)을 보냈다. [2] 소켓 인터페이스 구현( linux/net/socket.c:1027-1054) 중 발췌. ( GPL 라이센스 및 커밋 3c435c) [3] 에러가 발생했을 때는 에러 코드 및 에러 메시지를 출력한 후에 프로그램을 그 자리에서 종료시켜야 하는 경우가 많다. 계속 실행시켰다가는 에러가 연속적으로 발생하거나 예기치 못한 결과가 나올 수도 있기 때문이다. 이때 goto 문은 프로그램의 가장 끝줄로 점프시키는 용도로도 사용할 수 있다. 위의 소스코드도 에러 예외처리로 GOTO를 사용한 예이다. [4] 리누스 토르발스는 대표적인 GOTO문 옹호자 중의 한 명이다. [5] 주의해야될 것은 이는 일반적인 C++을 이용한 개발일 때이다. 임베디드나 커널 환경에서 try-catch을 무작정 사용하는 것은 다 된 밥에 재를 뿌리는 것처럼 엄청난 트롤 행동이다. try-catch의 기본적으로 몇천에서 몇만 사이클까지 소요된다. 끽해봐야 몇천 사이클이라고 무시할 수는 없는데, 168Mhz MCU 기준으로 약 30us가 소요된다. 끽해봐야 30us라 할 수 있겠지만, 결국 해당 실행이 완료되기 전까지 인터럽트를 제외한 런타임 코드 실행에 문제가 발생한다. 당연하게도 GOTO 문 사용은 예외처리할 경우 권장하며, 리눅스 외에도 macOS 드라이버 개발 API인 DriverKit 공식 예제에도 goto문을 이용한 예외처리가 나와있다. [6] 아키텍처에 따라 Branch로 쓰기도 함