그러나, 이렇게만 하면 재귀가 종료가 되질 않는다. 음수 아래로 계속 끊기지 않고 연산할 수 있기 때문이다. 그렇기 때문에 이 재귀가 종료될 수 있는 조건을 제시해 줘야 한다. 여기에서는 1을 대입하게 되면 그대로 합이 1이 된다. 이걸 정의하면 1이 되었을 때 1을 할당하고 종료시킬 수 있게 된다. 이 내용을 하나의 내용으로 정리하면 아래와 같다.
sum(n)
1, n=1
n + sum(n-1), n > 1
이를 C 함수로 나타내게 되면, n=1이면 1을 반환하고, 그렇지 않으면 n+sum(n-1)을 반환하도록 한다. 조건식으로 충분히 나눌 수 있고, 조건식 처리로 재귀 종료를 확인할 수 있는 것이다.
C언어에서는 임의의 함수에서 자기 자신을 호출할 수 있는데, 이를 재귀 호출(recursion)이라고 한다. 이러한 재귀 호출을 사용하는 예가 다양하게 있는데, 그냥 간단한 예시 하나나 둘 정도 보면서 재귀 호출에 대해서 살펴보도록 하겠다.
일단 다음과 같은 구조를 보자.
void r_func();
main()
{
r_func();
}
void r_func()
{
r_func();
}
main 함수에서 함수 r_func를 호출하면 제어가 r_func로 넘어간다. 그런데 r_func에서 다시 r_func를 호출하는 과정이 계속 반복된다. 이와 같이 자신을 호출하는 것을 재귀 호출이라고 한다. 그런데 위와 같은 프로그램은 실행이 종료되지 않고 계속 반복되므로 재귀 호출 함수는 꼭 재귀 호출이 종료되는 조건을 설정해 주어야 한다. 그 설정을 만들어 주는 과정이 어렵기 때문에 동작 순서를 제대로 구성하지 않으면 안된다. 알고리즘에서 중요하게 다루는 이유가 이런 이유다.
함수의 기억 클래스는 외부(extern) 함수와 정적(static) 함수로 나뉜다. 외부 함수는 분리 컴파일 시 다른 파일에서 참조할 수 있으며, 기억 클래스 자리에 아무 내용이 없으면 외부 함수임을 의미한다. 반면에 정적 함수는 분리 컴파일 시 다른 파일에서 참조할 수 없으며, 기억 클래스 자리에 static을 작성하면 정적 함수임을 의미한다. 이 두 가지를 모두 보여주는 예로, 정의한 적정 함수를 호출은 하지만 사용이 불가능하다.
자동 변수는 그 함수 내에서만 사용되며 함수에서 벗어나면 소멸하지만, 정적 변수(static variable)는 데이터 영역 이 기억 영역에 영구적으로 존재한다. 그리고 사용 용도에 따라 내부 정적 변수와 외부 정적 변수로 나뉜다.
내부 정적 변수: 함수 안에서 선언된 정적 변수
외부 정적 변수: 함수 밖에서 선언된 정적 변수
먼저, 내부 정적 변수는 함수 안에서 선언되며 그 함수 안에서만 사용할 수 있다. 그러나 자동 변수와 달리 그 함수를 빠져 나왔다가 다시 들어가도 이전 값이 그대로 유지된다. 그 예시가 아래와 같다.
그리고 외부 정적 변수는 함수 외부에서 정적으로 선언되는 변수로, 분리 컴파일 시 변수가 선언된 파일 내에서는 참조가 가능하지만 다른 파일에서는 참조할 수 없다. 다음은 이에 대한 예시로, 외부에서 선언한 정적 변수 count를 다른 파일에서 사용하려 하지만 사용이 불가능하다.
일반적으로 변수는 메모리에 생성되지만, 레지스터 변수(register variable)는 메모리가 아닌 레지스터에 생성된다. 그런데 이러한 레지스터를 사용하면 메모리에서보다 더 빨리 처리할 수 있으므로 레지스터 변수는 반복문 제어 등 사용 빈도가 높은 변수에 이용하면 효율적이다. 그러나 다음과 같이 레지스터 변수를 선언해도 레지스터 수에는 한계가 있으므로 레지스터로 할당하지 못하는 경우에는 자동 변수로 처리가 된다는 점이 있다.
함수간의 변수를 공유하는 가장 대표적인 방법은 외부 변수를 사용하는 것이다. 외부 변수는 함수 밖에서 선언되고 어느 함수에서든 사용할 수 있으며, 생성된 외부 변수는 프로그램 실행이 종료될 때까지 메모리에서 사라지지 않는다.
아래의 예시 프로그램이 바로 외부 변수를 사용하는 예시로, 여기서는 외부 변수 count를 main 함수와 func 함수에서 공우하고 있다.
그리고 C언어에서는 하나의 프로그램을 여러 파일로 분리해 컴파일 할 수도 있는데, 이렇게 하면 큰 규모의 프로그램을 작성할 때 매우 유용하다. 그리고 이렇게 여러 파일로 분리해 프로그램을 작성할 때 외부 변수를 이용하면 여러 파일이 변수를 공유할 수 있다.
이번 예시 프로그램은 두 개의 파일로 구성된 프로그램이다. 외부 변수 count를 선언하고 외부 변수 count를 다른 파일에서 재선언한다. 이렇게 재선언하는 것은 어디에선가 선언된 외부 변수 count를 사용하겠다는 의미로 결국 다른 파일에서 선언한 외부 변수를 이용할 수 있게 되는 것이다. 그때 사용한 것이 extern이다.
함수가 제대로 동작하려면 한 함수에서 다른 함수를 호출할 때 필요한 값을 전달해주기도 하고 결과값을 전달받기도 해야 하는데, 함수들끼리 주고받는 데이터를 매개변수(parameter)라고 한다. 그리고 특별히 함수를 호출하는 쪽의 매개변수를 실매개변수(actual parameter)라고 하고, 함수를 정의한 쪽의 매개변수를 형식매개변수(formal parameter)라고 한다.
C언어에서 함수를 호출할 때 매개변수를 전달하는 방법에는 다음 두 가지가 있다. 하나하나 살펴본다.
우선 ‘값에 의한 전달(call by value)’는 함수 호출 시 값만 함수쪽으로 보내 해당 값을 매개변수에 저장해 동작하는 방법으로, 함수 실행중에 형식매개변수의 내용이 변해도 실매개변수의 니용은 전혀 변하지 않는다. 즉, 값이 그대로 함수쪽의 매개변수에 복사되어 별도의 변수처럼 취급되는 것이다.
그리고 ‘주소에 의한 전달(call by address)’ (call by reference라고도 합니다.)은 값을 전달하는 것이 아닌 전달하고자 하는 변수의 메모리 주소를 전달하는 방법으로, 함수 실행중에 형식 매개변수 값의 조정에 따라 실매개변수의 내용이 변경되는 방법이다. 당연히 포인터에 대한 개념이 제대로 있어야 쓸 수 있다.
이 두 가지를 직접 하나의 예제에 박아서 작성하였다. 함수명에도 적혀있지만 call by value, call by address(call by reference) 형식으로 전달하는 방법을 보여주는데, 결과값이 확실히 다른 것을 볼 수 있다.
call by value로 호출한 함수의 경우에는 두 변수의 값을 바꾸는 작업을 하였는데도 불구하고 함수에서 쓰인 변수와 main에 선언한 변수는 별개의 값으로 취급되기 때문에 둘의 값을 바꾼 건 함수 안에서만의 동작이고 실제로는 바뀌지 않았다. 그러나 call by address의 경우에는 main에서 선언한 변수를 포인터로 가져와서 치완하는 작업을 진행하였기 때문에 해당하는 값을 그대로 바꿨다. 그래서 출력 결과가 바뀌어 있는 것이다.