C 함수 마스터: 선언, 정의, 호출, 인자 전달(값/참조), 재귀
함수는 코드를 재사용 가능한 단위로 캡슐화합니다. C에서 함수의 구조, 선언/정의 분리, 매개변수 전달 방식, 재귀 호출까지 체계적으로 정리합니다.
함수 구조
반환타입 함수이름(매개변수목록) {
// 함수 본문
변수 선언;
문장들;
return 표현식; // void가 아닐 때 필수
}
구성 요소
반환 타입: int, double, void, 포인터, 구조체 등
함수 이름: 식별자 규칙 따름
매개변수 목록: 타입 이름, 타입 이름, ... 또는 void (인자 없음 명시)
함수 본문: 블록 { ... }, 지역 변수 선언 + 문장들
함수 선언 (프로토타입) vs 정의
| 구분 | 선언 (Prototype) | 정의 (Definition) |
| 목적 | 컴파일러에 인터페이스 알림 | 실제 구현 제공 |
| 위치 | 헤더 파일(.h) 또는 파일 상단 | .c 파일 |
| 세미콜론 | 필요 ; | 불필요 (블록으로 끝) |
| 매개변수 이름 | 생략 가능 (int max(int, int);) | 필수 |
| 빈도 | 여러 파일에서 포함 가능 | 전체 프로그램에 한 번만 |
예제
// max.h (헤더)
#ifndef MAX_H
#define MAX_H
int max(int a, int b); // 선언
#endif
// max.c (구현)
#include "max.h"
int max(int a, int b) { // 정의
return (a > b) ? a : b;
}
// main.c (사용)
#include <stdio.h>
#include "max.h"
int main() {
printf("%d\n", max(10, 20)); // 20
}
함수 호출 메커니즘
인자 평가 (순서 불명확, C 표준상 unspecified)
스택 프레임 생성: 반환 주소, 지역 변수, 매개변수 저장
제어권 이동 (칼리 호출)
함수 실행
return 또는 본문 끝 도달 시 반환값 설정 후 스택 프레임 해제
호출자 복귀, 반환값 수신
인자 전달 방식
Call by Value (값 전달) - C 기본
void swap_val(int a, int b) {
int tmp = a; a = b; b = tmp; // 지역 복사본만 변경
}
int main() {
int x = 10, y = 20;
swap_val(x, y);
printf("%d %d\n", x, y); // 10 20 (변화 없음)
}
Call by Reference (참조 전달) - 포인터로 시뮬레이션
void swap_ref(int *a, int *b) {
int tmp = *a; *a = *b; *b = tmp; // 원본 수정
}
int main() {
int x = 10, y = 20;
swap_ref(&x, &y); // 주소 전달
printf("%d %d\n", x, y); // 20 10 (교환됨)
}
배열은 암묵적 참조 전달
void modify(int arr[], int n) { // 실제로는 int *arr
arr[0] = 999; // 원본 배열 변경됨
}
// 호출: modify(arr, size);
재귀 함수 (Recursive Function)
예제: 팩토리얼, 피보나치
// 팩토리얼
unsigned long long factorial(int n) {
if (n <= 1) return 1; // 기저 조건
return n * factorial(n - 1);
}
// 피보나치 (비효율적: 지수 시간)
unsigned long long fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
// 꼬리 재귀 (컴파일러 최적화 시 스택 상수)
unsigned long long fib_tail(int n, unsigned long long a, unsigned long long b) {
if (n == 0) return a;
return fib_tail(n-1, b, a+b);
}
// 호출: fib_tail(n, 0, 1);
함수 포인터
// 함수 포인터 타입 정의
typedef int (*BinaryOp)(int, int);
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int calculate(int a, int b, BinaryOp op) {
return op(a, b);
}
int main() {
printf("%d\n", calculate(3, 4, add)); // 7
printf("%d\n", calculate(3, 4, mul)); // 12
}
인라인 함수 (C99)
// 헤더 파일에 정의 (inline + static 또는 extern inline 패턴)
static inline int square(int x) {
return x * x;
}
함수 호출 오버헤드 제거 위해 본문 치환 힌트
static inline 헤더 정의 권장 (다중 정의 허용, 내부 링키지)
컴파일러가 판단하여 인라인 여부 결정 (-O2/-O3에서 적극적)
가변 인자 함수 (<stdarg.h>)
#include <stdarg.h>
#include <stdio.h>
void print_numbers(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
printf("%d ", va_arg(args, int));
}
va_end(args);
printf("\n");
}
// 호출: print_numbers(3, 10, 20, 30);
모범 사례
단일 책임: 함수는 한 가지 일만 잘 하게
작게 유지: 화면 한 페이지 내외 (50줄 이하 권장)
인자 4개 이하 권장, 많으면 구조체로 묶기
const 매개변수로 읽기 전용 의도 표현
반환값으로 오류 코드 대신 bool 성공/실패 + 출력 매개변수 패턴 고려
재귀 깊이 제한적일 때만 사용, 꼬리 재귀 최적화 확인
함수 포인터로 콜백/전략 패턴 구현