실기 C: 함수 호출·재귀·static 추적
쉽게 말하면
실기 C 문제는 프로그램을 주고 '출력이 뭔가요?'라고 물어요. 머릿속으로만 따라가면 꼭 한 군데서 틀려요. 그래서 종이에 변수 이름을 가로로 적고, 한 줄 실행할 때마다 바뀐 값을 아래로 적어 가는 추적표를 그려요. 특히 잘 틀리는 곳은 static 변수(함수가 끝나도 값이 남음), 포인터로 넘긴 값(원래 변수가 바뀜), 재귀(끝까지 내려갔다가 되돌아오며 계산), break 없는 switch(아래 case로 계속 내려감)예요.
시험용 정의
C 출력 문제는 추적표로 푼다: 변수를 열로 적고 문장이 실행될 때마다 값을 한 줄씩 적는다. 자주 나오는 함정: static 지역 변수는 프로그램 시작 때 한 번만 초기화되고 호출이 끝나도 값이 유지된다. 전역 변수는 모든 함수가 함께 쓴다. C의 함수 인자는 값에 의한 호출(복사)이라 함수 안에서 바꿔도 원래 변수는 그대로지만, 주소(포인터)를 넘기면 *p로 원래 변수를 바꿀 수 있다. 재귀는 종료 조건까지 내려간 뒤 돌아오면서 계산하므로, 호출을 한 줄씩 펼쳐 적는다. switch는 break가 없으면 아래 case를 계속 실행한다(낙하, fall-through). 정수끼리 나누면 몫만 남고(7 / 2 = 3), % 는 나머지다. i++는 쓰고 나서 증가, ++i는 증가하고 나서 쓴다. 비트 연산자 &(AND), |(OR), ^(XOR), ~(NOT), <<·>>(시프트)는 2진수로 바꿔 계산한다.
비유로 이해하기
추적표는 가계부와 같다. 머릿속으로 '대충 이 정도 남았겠지' 하면 틀리지만, 들어오고 나간 것을 한 줄씩 적으면 마지막 잔액이 정확히 나온다.
자세히
먼저 알아 둘 말
| 말 | 뜻 |
|---|---|
| 추적표 | 변수 값이 바뀌는 과정을 한 줄씩 적은 표 |
| 지역 변수 | 함수 안에서 만들어져 함수가 끝나면 사라지는 변수 |
| static 지역 변수 | 함수 안에 있지만 값이 프로그램 끝까지 남는 변수. 초기화는 한 번만 |
| 값에 의한 호출 | 인자 값을 복사해 넘김. 함수 안에서 바꿔도 원래 변수는 그대로 |
| 재귀 | 함수가 자기 자신을 다시 부르는 것. 종료 조건이 꼭 있어야 한다 |
| 낙하 (fall-through) | switch에서 break가 없어 다음 case까지 이어서 실행되는 것 |
예제 1: 추적표 그리기
int a = 1, b = 1, t;
for (int i = 0; i < 5; i++) {
t = a + b;
a = b;
b = t;
}
printf("%d %d\n", a, b); // 8 13| i | t | a | b |
|---|---|---|---|
| 시작 | - | 1 | 1 |
| 0 | 2 | 1 | 2 |
| 1 | 3 | 2 | 3 |
| 2 | 5 | 3 | 5 |
| 3 | 8 | 5 | 8 |
| 4 | 13 | 8 | 13 |
i가 5가 되면 조건이 거짓이라 끝나요. 피보나치 수열을 만드는 코드예요.
예제 2: static·전역 변수와 주소 전달
int g = 10; // 전역 변수
void f(void) {
static int s = 0; // 한 번만 초기화
int l = 0; // 매번 0
s++; l++; g--;
printf("%d %d %d\n", s, l, g);
}
void inc(int *p, int v) { *p += v; v += 100; }
int main(void) {
f(); f(); f(); // 1 1 9 / 2 1 8 / 3 1 7
int x = 5, y = 3;
inc(&x, y);
printf("%d %d\n", x, y); // 8 3
}x는 주소로 넘겨 원래 값이 바뀌고, y는 값으로 넘겨 함수 안에서 v를 바꿔도 그대로예요.
예제 3: 재귀 펼치기
int fact(int n) { return n <= 1 ? 1 : n * fact(n - 1); }
int fib(int n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
void p(int n) {
if (n == 0) return;
printf("%d", n);
p(n - 1);
printf("%d", n);
}
// fact(5) = 120, fib(7) = 13
// p(3) 출력: 321123p(3)은 3을 찍고 p(2)로 내려가요. 2, 1을 찍고 p(0)에서 돌아오면서 1, 2, 3을 다시 찍어요. 내려갈 때와 돌아올 때 할 일을 나눠 적으면 쉬워요.
예제 4: 연산자 함정
| 식 | 결과 | 이유 |
|---|---|---|
| 7 / 2, 7 % 2 | 3, 1 | 정수 나눗셈은 몫만, % 는 나머지 |
| i = 5; a = i++; b = ++i; | a = 5, b = 7 | i++는 쓰고 증가, ++i는 증가하고 씀 |
| 5 & 3, 5 | 3, 5 ^ 3 | 1, 7, 6 | 101과 011을 비트마다 AND, OR, XOR |
| 5 << 1, 5 >> 1 | 10, 2 | 왼쪽 시프트는 × 2, 오른쪽은 ÷ 2(몫) |
| ~5 | -6 | 비트를 모두 뒤집으면 −(n + 1) |
시험에 이렇게 나와요
- 추적표로 반복문 변수 값 따라가기
- static 지역 변수는 값 유지, 초기화 한 번
- 값 전달과 주소(포인터) 전달 차이
- 재귀는 내려갈 때와 돌아올 때를 나눠 펼치기
- break 없는 switch 낙하, i++와 ++i, 비트 연산
출제기준 기반
확인 문제
Q1. void f(void) { static int s = 0; s++; printf("%d ", s); }를 세 번 부르면 출력은?
- 1 2 3
- 1 1 1
- 0 1 2
- 3 3 3
정답 보기
1번 1 2 3
static 지역 변수는 처음 한 번만 0으로 초기화되고, 함수가 끝나도 값이 남아요. 그래서 1, 2, 3으로 커져요. static이 없으면 매번 0에서 시작해 1 1 1.
Q2. int x = 5; void f(int v) { v = 10; } 에서 f(x); 뒤 x의 값은?
- 10
- 0
- 알 수 없음
- 5
정답 보기
4번 5
값에 의한 호출이라 v는 x의 복사본이에요. x를 바꾸려면 f(&x)로 주소를 넘기고 *v = 10으로 써야 해요.
Q3. int f(int n) { if (n == 0) return 0; return n % 10 + f(n / 10); }일 때 f(345)의 값은?
- 3
- 12
- 345
- 543
정답 보기
2번 12
끝자리를 하나씩 떼어 더해요. 5 + 4 + 3 + f(0) = 12.
Q4. switch (2) { case 1: s += 1; case 2: s += 2; case 3: s += 3; break; default: s += 4; } 실행 뒤 s는? (처음 s = 0)
- 2
- 5
- 6
- 9
정답 보기
2번 5
case 2부터 시작해 break가 없으니 case 3까지 내려가 2 + 3 = 5. break에서 멈춰 default는 실행하지 않아요.
Q5. 12 ^ 5의 값은? (^ 는 XOR)
- 4
- 9
- 13
- 17
정답 보기
2번 9
1100 XOR 0101 = 1001 = 9. 같은 비트는 0, 다른 비트는 1.
헷갈리기 쉬운 O·X
static 지역 변수는 함수를 부를 때마다 다시 초기화된다.
정답 보기
X 처음 한 번만 초기화되고 값이 계속 남아요.
C에서 배열을 함수에 넘기면 첫 칸의 주소가 넘어가서, 함수 안에서 배열 값을 바꾸면 원래 배열도 바뀐다.
정답 보기
O
i = 3; a = ++i; 를 실행하면 a는 3이다.
정답 보기
X ++i는 먼저 증가하고 값을 써서 a는 4예요.