Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c9cbb90

Browse files
committed
✨ goto문 설명 완료
1 parent d48a0a4 commit c9cbb90

File tree

1 file changed

+165
-4
lines changed

1 file changed

+165
-4
lines changed

‎8-functions/README.md

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ int main()
4242

4343
지금까지 배운 컴퓨터의 구조를 정리해보겠습니다. RAM과 레지스터, 연산장치가 있습니다. 연산장치는 주어진 프로그램에 따라 RAM이나 레지스터에서 값을 넣고 빼고 연산합니다. 근데 그럼 프로그램은 어디에 있을까요? 프로그램도 일종의 정보인만큼 프로그램을 저장할 메모리가 필요해보입니다.
4444

45-
우리가 쓰는 컴퓨터는 프로그램도 RAM에 저장합니다. [1장](../1-prepare-development-environment)에서 컴파일러는 프로그램이 실행되기 전에 코드를 모두 읽고 번역한다고 했었습니다. 컴파일러가 코드를 번역한 결과는 정수의 배열 형태로 된 프로그램입니다. 그 프로그램을 실행하면 RAM에 그 프로그램이 전부 복사되게 되고, 그럼 연산장치가 RAM에 있는 배열 형태로 된 프로그램을 한 원소씩 읽어 실행할 수 있게 됩니다. 위 예제를 예로 들면, 저 프로그램이 `{ 0x48, 0x8D, 0x54, 0x24, 0x20, 0x48, 0x8D, 0x0D, 0x3F, 0x11, 0x00, 0x00, 0xE8, 0x7A, 0xFF, ... }` 라는 배열이 되어 RAM 어딘가에 존재하게 됩니다. 이 정수들은 무작위적인 숫자가 아니라, 별도의 변환과정 없이 연산장치의 회로가 인식할 수 있는 숫자들입니다. 이 숫자들의 규칙을 `기계어(machine code)`라고 합니다.
45+
우리가 쓰는 컴퓨터는 프로그램도 RAM에 저장됩니다. [1장](../1-prepare-development-environment)에서 컴파일러는 프로그램이 실행되기 전에 코드를 모두 읽고 번역한다고 했었습니다. 컴파일러가 코드를 번역한 결과는 정수의 배열 형태로 된 프로그램입니다. 그 프로그램을 실행하면 RAM에 그 프로그램이 전부 복사되게 되고, 그럼 연산장치가 RAM에 있는 배열 형태로 된 프로그램을 한 원소씩 읽어 실행할 수 있게 됩니다. 위 예제를 예로 들면, 저 프로그램이 `{ 0x48, 0x8D, 0x54, 0x24, 0x20, 0x48, 0x8D, 0x0D, 0x3F, 0x11, 0x00, 0x00, 0xE8, 0x7A, 0xFF, ... }` 라는 배열이 되어 RAM 어딘가에 존재하게 됩니다. 이 정수들은 무작위적인 숫자가 아니라, 별도의 변환과정 없이 연산장치의 회로가 인식할 수 있는 숫자들입니다. 이 숫자들의 규칙을 `기계어(machine code)`라고 합니다.
4646

4747
연산장치는 기계어 코드를 RAM에서 읽기 위해, 다음에 읽을 기계어 코드가 어디있는지 알아야 합니다. 연산장치는 다음에 읽을 기계어 코드가 저장된 공간의 주소도 레지스터에 저장하는데, 이 역할을 하는 레지스터를 프로그램 카운터라고 합니다.
4848

@@ -68,7 +68,7 @@ int main()
6868

6969
프로그램 카운터가 1, 2, 3(에 대응하는 메모리 주소)으로 변화해 간다는 사실을 알 수 있습니다.
7070

71-
이 프로그램 카운터는 특정한 문장으로 값을 변경하는 것이 가능합니다. [7장](../7-control-flow)에서 다양한 제어 흐름 문장들에 대해 배웠는데요, if문을 예로 들겠습니다.
71+
특정한 문장에 대응되는 주소를 프로그램 카운터에 대입할 수도 있습니다. [7장](../7-control-flow)에서 다양한 제어 흐름 문장들에 대해 배웠는데요, if문을 예로 들겠습니다.
7272

7373
```c
7474
#include <stdio.h>
@@ -93,7 +93,7 @@ int main()
9393
6.
9494
```
9595

96-
사용자의 입력이 `14`라고 가정해보겠습니다. 그럼 프로그램 카운터는 기본적으로 1씩 증가합니다. 그런데, 2번에서 `i < 5`가 거짓이기 때문에 프로그램 카운터가 5로 바뀝니다. 그래서 이 경우 프로그램 카운터가 가지고 있는 값은 1, 2, 5, 6으로 변화해 간다는 사실을 알 수 있습니다. 반대로 사용자의 입력이 `3`이라면 프로그램 카운터 속 숫자는 1, 2, 3, 4, 6으로 변화해 간다는 사실을 알 수 있습니다.
96+
사용자의 입력이 `14`라고 가정해보겠습니다. 그럼 프로그램 카운터는 기본적으로 1씩 증가합니다. 그런데, 2번에서 `i < 5`가 거짓이기 때문에 프로그램 카운터가 5로 바뀝니다. 그래서 이 경우 프로그램 카운터가 가지고 있는 값은 1, 2, 5, 6(에 대응하는 메모리 주소)으로 변화해 간다는 사실을 알 수 있습니다. 반대로 사용자의 입력이 `3`이라면 프로그램 카운터 속 숫자는 1, 2, 3, 4, 6으로 변화해 간다는 사실을 알 수 있습니다.
9797

9898
for문에 대해서도 동일한 분석이 가능합니다.
9999

@@ -116,4 +116,165 @@ int main()
116116
6.
117117
```
118118

119-
그럼 이 프로그램의 경우 프로그램 카운터가 1, 2, 3, 4, 5, 2, 3, 4, 5, ..., 3, 4, 5, 2, 6으로 변화해 간다는 사실을 알 수 있습니다.
119+
그럼 이 프로그램의 경우 프로그램 카운터가 1, 2, 3, 4, 5, 2, 3, 4, 5, ..., 3, 4, 5, 2, 6으로 변화해 간다는 사실을 알 수 있습니다.
120+
121+
## goto문(goto statement)과 레이블(label)
122+
123+
지금까지 배운 조건문이나 반복문들은 정해진 규칙대로만 프로그램 카운터가 가지고 있는 주소를 바꿀 수 있었습니다. 그런데, 프로그램 카운터가 프로그래머가 지정한 문장을 가리키게 하는 것도 가능합니다. C에선 이것을 `goto문(goto statement)``레이블(label)`이라는 기능으로 구현합니다. 이 둘은 각각 다음과 같이 사용합니다.
124+
125+
```
126+
goto <레이블 이름>;
127+
```
128+
```
129+
<레이블 이름>: <선언문이 아닌 문장>;
130+
```
131+
132+
선언문을 제외한 임의의 문장에 레이블로 이름을 달 수 있는 것을 알 수 있습니다.
133+
134+
> 사실 `선언문(declaration)``문장(statement)`가 아니지만, 선언문을 문장으로 보면 이해하기 더 쉬운 경우가 있기 때문에(`int main { ... }`안에는 항상 문장만 들어간다고 생각할 수 있는 등) 둘을 혼용해서 설명했었습니다. 이 경우는 선언문과 문장이 서로 다르게 취급되는 몇 안되는 경우입니다. 앞으로 설명의 편의를 위해 **선언****선언문**이라는 용어를 혼용하겠습니다. 둘 다 declaration을 번역한 용어라고 생각해주세요.
135+
136+
예제를 보겠습니다.
137+
138+
```c
139+
#include <stdio.h>
140+
141+
int main()
142+
{
143+
printf("Hello, ");
144+
goto world;
145+
printf("Python ");
146+
world:
147+
printf("world!");
148+
}
149+
```
150+
```
151+
Hello, world!
152+
```
153+
154+
`Python`이 출력되지 않았습니다. 왜냐하면 `goto world;`에 의해 프로그램 카운터가 가지고 있는 주소가 `printf("world!");`를 가리키도록 바뀌었기 때문입니다. 자세히 분석해보겠습니다.
155+
156+
```
157+
프로그램이 시작했을 때,
158+
1. "Hello, "를 출력합니다.
159+
2. world로 표시된 문장으로 이동합니다.
160+
3. "Python "을 출력합니다.
161+
4(world). "world!"를 출력합니다.
162+
```
163+
164+
위 예제는 이렇게 표현될 수 있습니다. 프로그램 카운터가 1, 2, 4로 바뀌었다는 사실을 쉽게 알 수 있습니다.
165+
166+
goto문을 통해 앞으로 이동하는 것도 가능합니다.
167+
168+
```c
169+
#include <stdio.h>
170+
171+
int main()
172+
{
173+
begin:
174+
printf("New line\n");
175+
goto begin;
176+
}
177+
```
178+
```
179+
New line
180+
New line
181+
New line
182+
New line
183+
New line
184+
New line
185+
New line
186+
...
187+
```
188+
189+
무한 루프가 발생하는 이유를 찾으실 수 있나요? 조건 없이 계속 앞 문장으로 이동하기 때문에 `New line`을 끊임없이 출력하게 됩니다. 자세히 분석해보겠습니다.
190+
191+
```
192+
프로그램이 시작했을 때,
193+
1(begin). "New line\n"을 출력합니다.
194+
2. begin으로 표시된 문장으로 이동합니다.
195+
```
196+
197+
프로그램 카운터가 1, 2, 1, 2, 1, 2, ...로 바뀐다는 것을 알 수 있습니다.
198+
199+
모든 반복문은 if문과 goto로 직접 만들 수 있습니다. 예를 들어 while문은 다음과 같이 바꿔 쓸 수 있습니다.
200+
201+
```
202+
while(<조건식>) <실행문>
203+
204+
->
205+
206+
begin:
207+
if (!(<조건식>)) goto end;
208+
<실행문>
209+
goto begin;
210+
end:;
211+
```
212+
213+
`end:` 다음에 세미콜론이 있는 것에 주목해주세요. 레이블 다음엔 반드시 문장이 와야하기 때문에, `end:` 다음에 아무것도 안 한다면 빈 문장으로 세미콜론 하나를 적어야 합니다.
214+
215+
goto문은 너무 많이 사용하면 코드가 읽기 힘들어지기 때문에, goto문을 사용해야하는가에 대한 논쟁이 많이 있었습니다. [이건](https://stackoverflow.com/questions/46586/goto-still-considered-harmful) StackOverflow의 한 질문인데, 무려 49개의 답변이 달린 것을 알 수 있습니다. goto가 정확히 어떤 일이 일으키는지 알고 쓰면 괜찮을 수 있지만, 실수를 일으키기 좋고 대부분의 경우 대체할 수 있는 방법이 있다라는 의견이 많이 보입니다. 즉, "위험성은 큰데 얻을 수 있는 건 적다"는 겁니다. 특히 이중에는 "goto문이 유일한 해법인 것처럼 보이는 경우는 사실 다른 부분의 디자인도 별로인 경우"라고 주장하는 사람도 있었습니다. 그래서 goto문은 되도록 사용을 지양하는 것이 좋을 것 같습니다.
216+
217+
물론 goto가 좋다고 생각되는 경우도 몇몇 있습니다. 반복문 안에 반복문을 쓴 경우가 그 예입니다. 3행 3열의 표에 숫자를 저장하되, 대각선 위에 있는 칸에는 음수를 저장하지 않는 프로그램을 만들고 싶습니다. 대각선 위에 있는 칸에 음수를 입력하면 프로그램을 강제종료하고자 합니다.
218+
219+
```c
220+
#include <stdio.h>
221+
222+
int main()
223+
{
224+
for (int i = 1; i <= 3; ++i)
225+
{
226+
for (int j = 1; j <= 3; ++j)
227+
{
228+
printf("%d %d\n", i, j);
229+
int input; scanf("%d", &input);
230+
if (input < 0 && i == j)
231+
goto end;
232+
}
233+
}
234+
end:;
235+
}
236+
```
237+
```
238+
1 1
239+
: 3
240+
1 2
241+
: -2
242+
1 3
243+
: -3
244+
2 1
245+
: -6
246+
2 2
247+
: -7
248+
```
249+
250+
원래는 break문을 두 번써야 하는 것을, goto문 한번으로 쉽게 반복문을 종료할 수 있다는 것을 알 수 있습니다. 이 예제를 break문을 활용해서 쓰려고 한다면
251+
252+
```c
253+
#include <stdio.h>
254+
255+
int main()
256+
{
257+
for (int i = 1; i <= 3; ++i)
258+
{
259+
int valid = 1;
260+
261+
for (int j = 1; j <= 3; ++j)
262+
{
263+
printf("%d %d\n", i, j);
264+
int input; scanf("%d", &input);
265+
if (input < 0 && i == j)
266+
{
267+
valid = 0;
268+
break;
269+
}
270+
}
271+
272+
if (!valid)
273+
break;
274+
}
275+
}
276+
```
277+
278+
이렇게 복잡하게 적어야합니다. `if (!valid) break;`가 없으면 `2 2`에서 음수를 입력했어도 `3 1`로 이동했을 것입니다.
279+
280+
> C++에선 RAII라는 것 때문에, goto를 쓰고 싶어도 쓰면 컴파일 오류가 발생하는 경우가 많습니다. RAII에 대해선 나중에 다루도록 하겠습니다.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /