2단원 — 함수
함수는 **이름이 붙은 코드 블록**입니다. 같은 작업을 한 곳에 모아 두면 중복이 줄고, 바꿀 때 한 군데만 고치면 됩니다.
이 강의에서 배우는 것
- 1함수의 선언(프로토타입)·정의·호출의 차이를 안다.
- 2매개변수 전달 방식(값 전달, 포인터 전달)을 구분한다.
- 3재귀 함수의 종료 조건을 설계한다.
- 4`static` 변수의 수명과 가시성을 이해한다.
왜 함수로 쪼개는가?
긴 `main` 하나에 모든 코드를 넣으면:
- **중복** 코드가 늘어남
- 버그 한 줄 고치려고 어디 있는지 찾기 힘듦
- 코드를 **재사용**하기 어려움
함수로 쪼개면 "이 함수는 평균을 구한다", "이 함수는 정렬한다"처럼 **역할 단위**로 코드를 다룰 수 있습니다.
핵심 개념
1) 함수의 3가지 모습
/* 1) 선언 (프로토타입) — main보다 위에 미리 알려줌 */
int add(int a, int b);
/* 2) 정의 — 실제 본문 */
int add(int a, int b) { return a + b; }
/* 3) 호출 */
int s = add(3, 4);매개변수 자료형, 반환형, 이름이 모두 일치해야 합니다.
2) 호출 스택의 흐름
int add(int a, int b) { return a + b; }
int main(void) {
int s = add(3, 4); // ← main이 add를 부르면
return 0; // add가 끝날 때까지 main은 멈춰 있음
}호출 시작 호출 종료
┌────────┐ ┌────────┐
│ add │ ◄── 현재 실행 │ main │ ◄── 현재 실행
├────────┤ └────────┘
│ main │
└────────┘
스택 (Last-In-First-Out)함수가 끝나면 **호출했던 곳**으로 정확히 돌아갑니다.
3) C는 항상 값 전달(call by value)
void inc(int x) { x++; } // 복사본을 1 증가시키고 끝
int n = 5;
inc(n); // n은 그대로 5호출자의 변수를 바꾸려면 **포인터를 전달**해야 합니다.
void inc_ptr(int *x) { (*x)++; }
inc_ptr(&n); // n은 64) 재귀 함수
long long factorial(int n) {
if (n <= 1) return 1; // 종료 조건 (base case)
return n * factorial(n - 1); // 자기 자신 호출
}**종료 조건**이 없거나 잘못되면 스택 오버플로(무한 호출).
5) `static`의 두 얼굴
- **함수 안의 `static` 변수**: 호출 사이에도 값이 유지 (수명이 프로그램 전체).
- **함수에 붙은 `static`**: 같은 파일 내에서만 호출 가능 (외부에 안 보임).
예제로 보기
예제 1 — `ex01_basic.c` : 함수의 선언/정의/호출
int add(int a, int b);
int max(int a, int b);
void print_line(void);**실행 결과**
--------------------
add(3, 4) = 7
max(7, 2) = 7
--------------------핵심: 프로토타입이 main 위에 있어 컴파일러가 호출 형식을 미리 압니다.
예제 2 — `ex02_recursion.c` : 팩토리얼과 피보나치
long long factorial(int n);
long long fib(int n);**실행 결과**
0! = 1, fib( 0) = 0
1! = 1, fib( 1) = 1
2! = 2, fib( 2) = 1
3! = 6, fib( 3) = 2
4! = 24, fib( 4) = 3
5! = 120, fib( 5) = 5
6! = 720, fib( 6) = 8
7! = 5040, fib( 7) = 13
8! = 40320, fib( 8) = 21
9! = 362880, fib( 9) = 34
10! = 3628800, fib(10) = 55핵심: 종료 조건(`n <= 1`)이 호출 트리의 **잎(leaf)** 입니다.
예제 3 — `ex03_array_param.c` : 배열을 함수로
int sum(const int arr[], int n) { ... }
double average(const int arr[], int n) { return (double)sum(arr,n) / n; }**실행 결과**
합계: 150
평균: 30.00핵심: 함수의 배열 매개변수는 사실상 **포인터**. 그래서 길이를 따로 받습니다.
예제 4 — `ex04_static.c` : 호출 사이에 값 유지
int counter(void) {
static int count = 0;
count++;
return count;
}**실행 결과**
호출 1 -> 1
호출 2 -> 2
호출 3 -> 3
호출 4 -> 4
호출 5 -> 5핵심: `static`이 없으면 매번 0으로 초기화되어 항상 1만 출력됩니다.
다른 시각으로 보기 — "함수는 자판기"
매개변수 (input) 반환값 (output)
▼ ▲
┌───────────────────────────┐
│ 함수 본문(블랙박스) │
└───────────────────────────┘같은 입력에는 같은 출력이 나오는 **결정론적 자판기**일수록 테스트와 재사용이 쉽습니다. 전역 변수 의존, 화면 출력 같은 부수 효과가 적을수록 좋습니다.
자주 하는 실수
- **반환형 누락**: `add(int a, int b)` 처럼 반환형을 빼면 옛날 C는 `int`로 가정 (지금은 경고).
- **선언 없이 호출**: 컴파일러가 추측해 잘못된 호출 규약을 쓸 수 있음.
- **재귀 종료 조건 누락**: 무한 재귀 → 스택 오버플로(런타임 충돌).
- **로컬 변수 주소 반환**: 함수 종료 후엔 무효 (`int *bad(void) { int x; return &x; }`).
- **배열 매개변수의 `sizeof`**: `void f(int a[10]) { sizeof(a); }` 결과는 배열 크기가 아닌 포인터 크기.
정리
- 함수는 역할 단위로 코드를 분리·재사용하는 도구.
- 호출은 호출 스택을 사용하며, 끝나면 호출자로 돌아온다.
- C는 값 전달. 호출자의 변수를 바꾸려면 포인터를 전달.
- 재귀는 반드시 종료 조건이 있어야 한다.
- `static` 지역 변수는 호출 사이에 값이 유지된다.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_basic.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_recursion.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_array_param.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_static.c && ./ex04응용:
- `ex02`의 `fib`을 반복문 버전으로 다시 작성해 성능을 비교해 보세요.
- `ex04`에서 `static`을 빼고 호출 결과가 어떻게 달라지는지 확인.
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
int add(int a, int b);
int max(int a, int b);
void print_line(void);
int main(void) {
print_line();
printf("add(3, 4) = %d\n", add(3, 4));
printf("max(7, 2) = %d\n", max(7, 2));
print_line();
return 0;
}
int add(int a, int b) { return a + b; }
int max(int a, int b) { return a > b ? a : b; }
void print_line(void) { printf("--------------------\n"); }
--------------------
add(3, 4) = 7
max(7, 2) = 7
--------------------#include <stdio.h>
long long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
long long fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
int main(void) {
for (int i = 0; i <= 10; i++) {
printf("%2d! = %lld, fib(%2d) = %lld\n",
i, factorial(i), i, fib(i));
}
return 0;
}
0! = 1, fib( 0) = 0
1! = 1, fib( 1) = 1
2! = 2, fib( 2) = 1
3! = 6, fib( 3) = 2
4! = 24, fib( 4) = 3
5! = 120, fib( 5) = 5
6! = 720, fib( 6) = 8
7! = 5040, fib( 7) = 13
8! = 40320, fib( 8) = 21
9! = 362880, fib( 9) = 34
10! = 3628800, fib(10) = 55#include <stdio.h>
/* 배열 매개변수는 사실상 포인터로 전달된다 */
int sum(const int arr[], int n) {
int s = 0;
for (int i = 0; i < n; i++) s += arr[i];
return s;
}
double average(const int arr[], int n) {
return (double)sum(arr, n) / n;
}
int main(void) {
int data[] = {10, 20, 30, 40, 50};
int n = (int)(sizeof(data) / sizeof(data[0]));
printf("합계: %d\n", sum(data, n));
printf("평균: %.2f\n", average(data, n));
return 0;
}
합계: 150
평균: 30.00#include <stdio.h>
int counter(void) {
static int count = 0; /* 호출 사이에 값이 유지됨 */
count++;
return count;
}
int main(void) {
for (int i = 0; i < 5; i++) {
printf("호출 %d -> %d\n", i + 1, counter());
}
return 0;
}
호출 1 -> 1
호출 2 -> 2
호출 3 -> 3
호출 4 -> 4
호출 5 -> 5📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 두 정수 a, b에 대해 **최대공약수(GCD)** 를 재귀로 구하는 함수 `int gcd(int a, int b)`를 작성하세요. (유클리드 호제법)
- 파일명: hw01.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int main(void) {
int a, b;
printf("두 정수: ");
scanf("%d %d", &a, &b);
printf("GCD(%d, %d) = %d\n", a, b, gcd(a, b));
return 0;
}
문제 2 (hw02.c)
목표: 정수 배열에서 **최댓값 인덱스**를 반환하는 함수 `int find_max_index(const int *arr, int n)`을 작성하세요.
- 파일명: hw02.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int find_max_index(const int *arr, int n) {
int idx = 0;
for (int i = 1; i < n; i++)
if (arr[i] > arr[idx]) idx = i;
return idx;
}
int main(void) {
int arr[] = {3, 7, 2, 8, 5, 8, 1};
int n = (int)(sizeof(arr) / sizeof(arr[0]));
int idx = find_max_index(arr, n);
printf("최댓값 %d는 인덱스 %d에 있습니다.\n", arr[idx], idx);
return 0;
}
문제 3 (hw03.c)
목표: 양의 정수 n을 받아 **2진수 문자열**을 출력하는 재귀 함수를 작성하세요. (`printBinary(13)` -> `1101`)
- 파일명: hw03.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
void printBinary(unsigned int n) {
if (n > 1) printBinary(n / 2);
putchar('0' + (n % 2));
}
int main(void) {
unsigned int n;
printf("양의 정수 입력: ");
scanf("%u", &n);
printBinary(n);
putchar('\n');
return 0;
}