"Goto"의 두 판 사이의 차이
8번째 줄: | 8번째 줄: | ||
=== 예시 === | === 예시 === | ||
==== 재입력 처리==== | ==== 재입력 처리==== | ||
− | 만약 1-3 값 만을 입력해야 하는 프로그램이 있다면, 그 이외의 값이 들어온다면 사용자가 값을 다시 입력하도록 프로그래밍 해야할 것이다. 여기에 사용되는 일반적인 방법은 [[do | + | 만약 1-3 값 만을 입력해야 하는 프로그램이 있다면, 그 이외의 값이 들어온다면 사용자가 값을 다시 입력하도록 프로그래밍 해야할 것이다. 여기에 사용되는 일반적인 방법은 [[do while]] 문을 사용하는 방법과 무한 loop에 진입시킨 이후에 옳은 값을 입력했다면 루프에서 탈출시키는 방법이다. |
int main() | int main() |
2021년 7월 15일 (목) 13:30 판
Goto(고투)는 영문을 풀어 해석하면 '~로 가다'라는 의미를 가지지만 프로그래밍에서는 어느 특정 줄 번호나 레이블로 건너뛰거나 돌아갈 때 쓰는 명령이다. 순간이동 같은 개념이라고 보면 된다.
Goto
프로그램의 흐름을 바꾸는 기본적인 명령으로, 일부 고급 언어에서 공통적으로 사용되는 명령이다. 원래는 CPU의 명령어 중에는 JUMP라는 것이 있는데, 이를 이용하면 코드의 특정 부분으로 바로 이동할 수 있다. 이것을 이용하면 조건분기나 반복 등을 구현할 수 있어서 많이 사용되는데, 이것을 고급 언어에 유사하게 사용할 수 있도록 구현한 것이 바로 GOTO다. 제대로 사용하면 상당히 유용하게 쓸 수도 있으나 보통의 IT 커뮤니티와 대부분의 개발자들 사이에서는 사용이 꺼려지는 기능이다. GOTO가 없는 언어도 많이 찾아볼 수 있는데, 몇 가지 이유가 있어서 그렇다. 일단 프로그래밍 언어 중 명령의 실행 순서를 중요하게 여기지 않는 언어들, 대표적으로 순수 함수형 언어(Haskell 등)와 일부 논리 프로그래밍 언어들(대표적으로 SQL)은 GOTO가 없다. 또한 일부 특수 목적 프로그래밍 언어 중에서 과거 시점으로 되돌아가는 게 불가능한 언어들(VHDL 등)이 있는데 이들 언어들에도 GOTO가 없다. 이들 언어들에는 앞으로도 영원히 GOTO가 추가될 일이 없는데, 언어의 서술 논리 자체에 '시간'이라는 개념이 없기 때문이다. 명령의 실행 순서가 중요하지만 GOTO를 지원하지 않는 경우도 있다. (대표적으로 Java의 경우 예약어로서는 존재하지만 기능이 없다.) 대부분의 프로그래밍 언어는 조건문과 반복문을 지원해주는데, 이를 이용하면 GOTO가 없어도 프로그램을 작성하는 데에 문제가 거의 없다. 게다가 아래에서 서술할 문제점 때문에 잘 지원하지 않는다. 참고로 GOTO는 동일 함수 내에서의 점프만 가능하다. 함수라는 개념이 없는 언어(어셈블리어, GW-BASIC 등)는 소스 코드 아무 곳으로나 점프가 가능하지만 함수 개념이 있는 언어에서는 함수 바깥의 레이블로 GOTO를 하려고 하면 컴파일 에러가 난다. 물론 이 함수도 뛰어넘어서 특정 코드로 이동하는 것이 아주 불가능한 것은 아니다. 예를 들어서 C/C++의 경우 setjmp, longjmp라는 함수를 제공하는데, 이것을 이용하면 함수를 넘어서 점프도 가능하다. 다만 이 함수가 하는 기능은 '현재의 스택의 상태와 코드의 위치'를 저장하여 특정 경우에 그 위치로 복귀하는 것이라서, 아직 setjmp를 만나지 않았거나 setjmp가 끝난 경우에는 점프할 수 없다. 무슨 말인지 이해가 잘 안 갈수도 있는데, C++의 try, catch, throw가 하는 역할과 비슷하다고 생각하면 된다.[1]
장점
GOTO문의 남용은 소스코드의 이해를 어렵게 만들지만, 적절한 사용은 오히려 소스코드의 가독성과 명료성을 높이는 경우가 있다. 가령 다중 반복문에서의 탈출, 에러에 대한 예외 처리 등 일부 작업에 한해서는 GOTO문을 사용하는 경우가 더 명료한 경우도 있다. 다만 위의 경우라도 로직 레벨에서의 탈출이나 함수화 시킨 후에 return등을 이용하는 등 GOTO문을 쓰지 않기를 권고하는 경우도 많다. 대표적인 예시로 리눅스 커널 소스코드를 까봐도 심심찮게 GOTO문을 발견할 수 있다.[4] 안정성이 매우 중요한 운영체제 커널에서 사용하는 것이다. 위의 코드도 리눅스 커널의 코드다. 터부시 되기에는 나름대로 쓰임새가 있는 것도 사실. 그러나 초보자에게는 많은 경우에 일종의 금기로써 가르친다. GOTO문이 유용한 경우는 어디까지나 특수한 케이스이고 잘못 사용했을 경우의 어디서부터 손대야할지 모르는 스파게티를 만들어버리는데다가 초보자의 경우 특히 가능성이 높기 때문. C언어보다 고수준 언어를 사용한다면 예외처리는 exception(try-catch)으로, C++같이 리소스 해제가 수동이지만 RAII를 지원하는 경우는 RAII를 사용하자. 용법은 같으면서 가독성은 증가한다. 다만 그런거 없는 C언어에서는 예외처리와 동적 리소스 해체에 goto를 주로 사용한다. 이 GOTO문은 어셈블리어의 산물이다보니 어셈블리어에서는 이러한 기능의 사용이 강제된다. 어셈블리어에서는 프로그램의 흐름을 제어할 수 있는 구문이 Jump 계열의 명령어밖에 없는데, 이는 무조건적으로 혹은 특정 조건을 만족하면 명령어에서 지시하는 프로그램 카운터로 이동하는 구문이다. 고급 언어로 쓰인 코드도 컴파일해서 어셈블리 소스를 열어보면 전부 GOTO문의 반복형으로 되어있다. 정확히는 jmp 계열 명령(jmp, jnz, jz 등. CPU의 아키텍처에 따라 다르다)으로 바뀐다.[1]
예시
재입력 처리
만약 1-3 값 만을 입력해야 하는 프로그램이 있다면, 그 이외의 값이 들어온다면 사용자가 값을 다시 입력하도록 프로그래밍 해야할 것이다. 여기에 사용되는 일반적인 방법은 do while 문을 사용하는 방법과 무한 loop에 진입시킨 이후에 옳은 값을 입력했다면 루프에서 탈출시키는 방법이다.
int main() { int i = 0; // do-while 문을 사용하는 방법 do { cout << "값을 입력하세요(1~3)(do-while) :: "; cin >> i; } while (i < 1 || i > 3); // 무한 loop를 사용하는 방법 while (true) { cout << "값을 입력하세요(1~3)(무한루프) :: "; cin >> i; if (i > 0 && i < 4)break; } }
이 경우에도 충분히 goto 문이 사용될 수 있다.
int main() { int i = 0; WRONGINPUT: cout << "값을 입력하세요(1~3)(무한루프) :: "; cin >> i; if (i < 1 || i > 3) goto WRONGINPUT; }
이렇듯 충분히 가독성을 해치지 않는 선을 지키면서도 goto 문을 사용할 수 있다. 프로그래밍을 하면서 중요한 점은 바로 이것이다. 프로그래밍에 필요한 모든 것을 최대한 활용하되 과하게 사용하지 말고 적절한 위치에 적절하게 사용하라는 것이다. 그것만으로도 코드는 충분히 깔끔해지고 작업 효율성이 상승하게 될 것이다.[2]
단점
고급 언어에서 GOTO문은 많은 커뮤니티에서 터부시되는 구문이다. 그 이유는 이게 너무 많이 쓰이다 보면 구조가 무너지고 코드가 황폐화되면서 어째 실행은 잘 되긴 되는데 코드를 짠 프로그래머 자신도 헷갈려 하는 소리가 절로 나오며 읽고 유지보수하기가 힘들어지는 지경에 이르게 된다. 이러한 상태로 된 코드를 '스파게티 코드'라고 한다. 이런 스파게티 스타일을 반대하고 구조적 프로그래밍을 주창한 네덜란드의 컴퓨터 과학자 에츠허르 다익스트라(Edsger Wybe Dijkstra)는 GOTO문의 사용이 프로그램의 정확성 분석과 증명 등의 측면에 해가 된다고 한 바 있다. 실제 프로그래밍할 때도 GOTO문을 남용하면 디버깅 불가+기능 추가 난해+다른 사람의 욕 처먹기+내가 이러려고 이 코드를 짰나 자괴감 들고 괴로운 사단 콤보가 일어날 수 있다. 또한 C언어에서 예외처리를 하기 위해 어쩔 수 없이 사용하는 경우도 있는데, setjmp, longjmp 같은 대용품이 존재한다. 이렇게 소스가 꼬이는 것은 사람에게만 문제가 되는 것이 아니다. 프로그램을 컴파일하면 최적화를 해야하는데, 언어 상에서 제공해주는 조건문/반복문 등은 자주 사용되거나 분기되는 특정 부분이 명확하기 때문에 최적화가 그리 어렵지는 않은 편이다. 하지만 GOTO를 사용할 경우 반복되는 부분이 명확해지지 않아서 최적화 결과가 좋지 않거나 시간이 오래 걸릴 수 있다.[1]
예시
void f() { int i = 10; if (i > 100) { I100: if (i < 200) { goto I200; } else if (i < 300) { goto I300; } } goto I100; I200: cout << 1 << endl; return; I300: cout << 2 << endl; return; }
위의 예시처럼 코드 이곳저곳을 뛰어넘게 되는 goto 문이 많을 경우나 goto I100; 부분처럼 조건에 맞지 않는 상황에서 조건문 안으로 강제 진입하게 만드는 goto 문이 있을 경우는 심각한 문제가 된다. 다른 작업자가 작업하게 되거나 작성자 자신이 재작업할 때, 코드의 흐름을 읽어내기가 어려워지고 조건문과 코드를 신뢰할 수 없게 될 것이다.[2]
각주
참고자료
- 〈GOTO - 나무위키〉, 《나무위키》
- wergia, 〈[프로그래밍 악마의 문법, goto :: 베르의 프로그래밍 노트]〉, 《티스토리》, 2017-06-01
같이 보기