본문 바로가기
카테고리 없음

C언어 - 함수와 재귀

by kik328288 2026. 5. 3.

C언어 함수와 매개변수, 재귀 호출 정리

프로그램의 규모가 커지면 한 곳에 모든 코드를 몰아넣는 방식으로는 더 이상 관리할 수 없게 된다. 같은 작업을 여러 곳에서 반복하게 되고, 코드를 수정할 때마다 누락이 생기며, 한 사람이 전체를 이해하는 일조차 어려워진다. 이러한 문제를 해결하기 위해 등장한 도구가 바로 함수(Function)이며, C언어를 비롯한 모든 절차형 언어의 가장 기본적인 추상화 단위이다. 본 글은 함수의 정의 방법과 매개변수 전달 방식, 그리고 자기 자신을 호출하는 특별한 형태인 재귀 함수의 동작 원리까지 한 번에 정리한다.

 

함수의 개념과 정의 방법

함수란 특정 작업을 수행하는 코드를 하나의 이름으로 묶어 놓은 단위이다. 같은 코드를 여러 번 작성할 필요 없이 함수 이름으로 호출할 수 있게 함으로써 코드의 재사용성을 극대화하고, 큰 프로그램을 작은 단위로 분할해 가독성과 유지보수성을 향상시킨다. 또한 한 함수는 한 가지 일만 수행하도록 작성하는 단일 책임 원칙(Single Responsibility Principle)을 따를 때 가장 좋은 함수가 된다. 우리가 자주 사용하는 printf와 scanf도 모두 C 표준 라이브러리에 정의된 함수이며, 매번 출력 로직을 새로 작성하지 않아도 되는 이유가 바로 함수의 재사용성에 있다.

C언어에서 함수는 크게 선언(Declaration)과 정의(Definition) 두 단계로 작성된다. 선언은 함수가 어떤 형태인지를 컴파일러에 미리 알리는 작업으로, "반환형 함수명(매개변수 목록);" 형식의 함수 원형(Prototype)으로 작성된다. 일반적으로 헤더 파일이나 소스 파일 상단에 위치시키며, main 함수 위에 둠으로써 본문에서 자유롭게 호출할 수 있게 만든다. 정의는 실제 함수의 본체를 작성하는 작업으로, "반환형 함수명(매개변수 목록) { 실행할 코드 }" 형식이다. 함수의 본문에서는 변수 선언, 연산, 다른 함수 호출, return 문이 자유롭게 사용될 수 있다.

함수는 반환형(Return Type), 함수명(Function Name), 매개변수 목록(Parameter List), 본문(Body)이라는 네 가지 요소로 구성된다. 반환형은 함수가 호출자에게 돌려주는 값의 자료형을 명시하며, 값을 돌려주지 않는 경우 void를 사용한다. 매개변수 목록은 함수가 호출될 때 받아들이는 값들의 자료형과 이름을 나열하며, 받지 않는 경우 비워두거나 void를 명시한다. 본문에서 return 문을 만나면 즉시 함수가 종료되고 호출자에게 값이 반환된다. 가장 단순한 형태의 함수는 main 함수이며, "int main(void) { return 0; }"의 모든 C 프로그램의 진입점이다. 운영체제는 프로그램 실행 시 main 함수를 호출하고, main이 반환하는 값을 종료 코드로 전달받는다.

매개변수 전달 방식, 값 호출과 주소 호출

함수에 데이터를 전달할 때는 호출자가 가지고 있는 값을 함수의 매개변수에 어떻게 넘길 것인가가 중요한 설계 결정이 된다. C언어는 두 가지 전달 방식을 지원하며, 시험과 실무 모두에서 자주 출제되는 핵심 개념이다. 두 방식의 차이를 정확히 이해하지 못하면 함수 호출 후 변경된 값이 호출자에게 반영되지 않는 미묘한 버그에 빠지기 쉽다.

값에 의한 호출(Call by Value)은 매개변수에 전달된 값의 복사본을 함수가 받아 사용하는 방식이다. 호출자의 변수와 함수 내부의 매개변수는 메모리상 완전히 별개의 공간에 존재하며, 함수 내부에서 매개변수의 값을 아무리 수정해도 호출자의 원본 변수에는 어떠한 영향도 미치지 않는다. 함수가 종료되면 매개변수가 차지하던 스택 공간이 자동으로 해제되어 사라진다. 이 방식은 호출자의 데이터를 보호한다는 강점이 있으며, 의도하지 않은 부수 효과(Side Effect)를 방지할 수 있다. C언어의 모든 함수 호출은 기본적으로 값에 의한 호출 방식을 따른다.

참조에 의한 호출(Call by Reference)은 매개변수에 변수의 주소값을 전달하는 방식이다. 함수는 그 주소를 통해 호출자의 원본 변수에 직접 접근할 수 있으며, 함수 내부의 변경이 호출자의 변수에 그대로 반영된다. 엄밀히 말하면 C언어는 참조에 의한 호출을 직접 지원하지 않으며, 포인터(Pointer)와 주소 연산자(&)를 활용해 그 효과를 만들어내는 방식이다. 두 변수의 값을 서로 바꾸는 swap 함수가 대표적인 예시로, 매개변수로 두 정수의 주소를 받아 포인터 역참조 연산자(*)를 통해 원본을 직접 수정한다. 또한 큰 구조체나 배열을 함수에 전달할 때 값을 통째로 복사하면 비용이 매우 크기 때문에, 주소만 전달해 효율을 높이는 용도로도 폭넓게 사용된다.

C언어에서 배열은 함수에 전달될 때 자동으로 첫 원소의 주소로 변환되어 전달된다. 따라서 함수 매개변수로 "int arr[]"라고 작성한 것과 "int *arr"라고 작성한 것은 컴파일러 입장에서 완전히 동일하며, 함수 내부에서 배열을 수정하면 호출자의 원본 배열도 함께 변경된다. 이 특성을 모르면 배열을 다루는 함수에서 의도와 다른 동작이 일어나기 쉽다. 호출자의 원본을 보호하면서 배열을 함수에 전달하고 싶다면 const 키워드를 매개변수에 붙여 함수 내부에서 수정하지 못하도록 강제할 수 있다.

재귀 함수의 동작 원리와 활용

재귀 함수(Recursive Function)란 자기 자신을 호출하는 함수를 의미한다. 처음 접하면 코드가 무한히 반복될 것 같은 두려움이 들지만, 실제로는 종료 조건(Base Case)을 명확히 정의하면 정해진 횟수만큼 호출되고 결과를 만들어낸다. 재귀는 본질적으로 큰 문제를 같은 형태의 작은 문제로 분할해 해결하는 사고방식이며, 트리 탐색·정렬 알고리즘·수학적 정의 같은 분야에서 매우 자연스러운 표현 방식을 제공한다.

재귀 함수가 동작하는 원리는 호출 스택(Call Stack)에 있다. 함수가 호출될 때마다 그 함수의 매개변수, 지역 변수, 반환 주소가 담긴 스택 프레임(Stack Frame)이 호출 스택의 가장 위에 쌓인다. 재귀 호출이 발생하면 같은 함수의 새로운 스택 프레임이 다시 쌓이며, 종료 조건을 만족해 반환이 시작되면 스택 프레임이 차례대로 해제되면서 결과 값이 거꾸로 전달된다. 가장 대표적인 재귀 함수는 팩토리얼 계산이다. n!을 구하는 함수를 정의하면 "factorial(n) = n × factorial(n-1)"이며, "factorial(1) = 1"이라는 종료 조건이 있다. 호출 시 5! → 5 × 4! → 5 × 4 × 3! → ... → 5 × 4 × 3 × 2 × 1 = 120 순서로 계산이 진행된다.

재귀 함수를 작성할 때는 두 가지를 반드시 지켜야 한다. 첫째, 종료 조건이 반드시 존재해야 하며 모든 재귀 호출이 결국 그 조건에 도달해야 한다. 종료 조건이 없거나 도달할 수 없는 형태로 작성된 재귀 함수는 호출 스택을 끝없이 쌓다가 결국 스택 오버플로(Stack Overflow) 오류를 일으키며 프로그램이 중단된다. 둘째, 매번 호출이 종료 조건에 가까워지는 방향으로 진행되어야 한다. n에 대한 재귀라면 매 호출마다 n이 작아지거나 단순해지는 형태가 되어야 한다는 뜻이다.

재귀가 자연스러운 문제와 그렇지 않은 문제는 분명히 구분된다. 피보나치 수열, 팩토리얼, 하노이의 탑, 트리 순회, 분할 정복 알고리즘 같은 수학적·재귀적 정의를 가진 문제는 재귀로 표현하는 것이 코드를 가장 단순하고 직관적으로 만든다. 반면 단순 반복으로 충분히 표현되는 작업이나 깊이가 매우 커질 수 있는 작업은 재귀보다 반복문이 더 안전하고 효율적이다. 재귀는 매 호출마다 함수 호출 비용이 발생하기 때문에 같은 알고리즘이라도 반복문 구현에 비해 다소 느리며, 깊이가 너무 커지면 스택 오버플로 위험까지 안고 있기 때문이다. 따라서 좋은 프로그래머는 재귀로 표현했을 때 가독성이 명확히 향상되는 경우에만 재귀를 선택하고, 성능이 중요한 영역에서는 같은 알고리즘을 반복문으로 변환하는 능력을 함께 갖추는 것이 권장된다. 결국 재귀 함수는 사고의 도구이자 표현의 도구이며, 그 동작 원리를 정확히 이해할 때 비로소 자유자재로 활용할 수 있는 강력한 무기가 된다.


메타 디스크립션: C언어의 핵심 추상화 단위인 함수의 정의 방법, 매개변수 전달 방식인 값 호출(Call by Value)과 주소 호출(Call by Reference)의 차이, 그리고 재귀 함수의 동작 원리와 활용을 입문자 관점에서 정리합니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름