[C/C++] 가변인수를 받는 함수

C함수의 가변인수

C함수도 가변인수를 사용할 수 있다. 대표적인 예가 printf이다. printf에는 서식 문자열을 비롯하여 서식에 채워질 값들을 컴마로 연결하여 나열할 수 있다. 이러한 가변인수는 어떻게 만들어서 사용할 수 있는지 살펴보자.

가변인수 함수의 선언

가변 인수 함수는 가변적으로 받을 인수 대신 을 사용하는 것으로 가변 인수 함수임을 컴파일러에게 알릴 수 있다. 인수에 …이 들어있는 경우 컴파일러는 인수의 개수나 타입에 대해서는 전혀 신경을 쓰지 않게 된다. (결국 이는 함수 구현 시 프로그래머가 일일이 체크해야 하는 부분이다.) 이 때 중요한 것은 고정적으로 사용하는 인수는 반드시 하나 이상 있어야 한다. 즉 int myFunc(…) 과 같이 쓰는 것은 안된다. 최소한 하나의 인수는 고정적으로 정의되어 있어야 한다. 올바른 가변 인수 함수의 선언은 int myFunc(int argc, …) 과 같은 모양이 되어야 한다.

가변 인수의 사용

컴파일러는 가변 인수에 몇 개의 인수가 들어오는지 전혀 신경쓰지 않는다. 따라서 가변이라고 하더라도 몇 개의 인수를 받을지는 알 수 있는 방법이 있어야 한다. 대부분의 가변 인수는 고정인수로 가변 인수의 개수를 받는 편을 택한다.

가변 인수의 획득

가변인수는 va_list 타입의 지역변수 ap를 선언하고 이를 통해 개별 인수를 획득할 수 있다. 다음과 같은 방식을 사용한다.

/*가변인수 함수 내*/

va_list ap;
va_start(ap,마지막_고정인수);
루프시작{
    arg = va_arg(ap, 변수의 타입);
    /*모든 가변 인수를 획득할 때 까지 이 루프를 반복한다*/
}
va_end(ap);

마지막 고정인수는 가변 인수를 획득하기 위해 꼭 필요하다. 왜냐하면 가변인수는 포인터 배열로 전달되기 때문에 시작번지를 알고 있어야 한다. 그 가변 인수의 시작번지는 마지막 고정인수의 번지의 바로 옆 위치가 되기 때문이다. (*함수의 인수로 전달된 변수들은 해당 함수의 지역 변수와 동등하게 취급된다.)

이렇게 루프를 돌며 각 변수의 타입을 지정하여 해당 인수를 획득할 수 있다. 특이한 점은, va_arg()라는 함수가 변수의 타입을 인수로 받고 있는데, 정상적인 C문법에서는 변수 타입을 인수로 받을 수 없다. 아마 C함수가 아니라 매크로로 짜여진 함수가 아닐까 생각된다. 즉 ap는 문자열형 포인터 변수이고, 루프를 돌면서 해당 포인터에서 지정된 타입형의 크기만큼의 값을 읽어서 그 내용을 꺼내주는 역할을 할 것이다.

이렇게 모든 가변 인수를 획득한 다음, va_end(ap); 구문을 써서 마무리 작업을 해줘야 한다. (사실 할 필요는 없는데 혹시 모르니.)

이 때, 계속해서 강조하는 것은 컴파일러는 어디서부터 어디까지가 인수로 넘겨진 값인지 알 수 없으니, 적절한 개수만큼의 인수를 얻어와야 한다. 가장 쉬운 방법은 고정 인수 중 하나에 가변 인수의 개수를 알려주는 방법이 있고, 혹은 문자열 배열을 처리하는 것과 마찬가지로 0 혹은 -1과 같이 특이한 값을 마지막 가변인수로 넣어주도록 하는 방법이 있을 수 있다.

다음은 가변 인수를 통해 함수에 집어 넣는 숫자들을 모두 더한 합을 리턴해주는 예제이다. 고정 인수로 가변 인수의 개수를 넘겨주는 방식을 택했다. va_list를 사용하기 위해서는 stdarg.h 를 반입해야 한다.

#include <stdio.h>
#include <stdarg.h>

int getSum(int num, ...)
{
	int sum = 0;
	int i;
	va_list ap;
	int arg;

	va_start(ap, num);
	for(i=0;i<num;i++){
		arg = va_arg(ap,int);
		sum += arg;
	}

	va_end(ap);
	return sum;

}

int main(void)
{
	printf("1+2=%d\n",getSum(2,1,2));
	printf("1+2+3+4+5=%d\n",getSum(5,1,2,3,4,5));

	return 0;
}

 그럼 printf는 어떻게 하고 있나

처음에 언급했던 printf는 그럼 어떻게 하고있나? 이 함수를 쓸 때는 가변인수의 개수를 명시하지도 않고 있고, 심지어 가변 인수의 타입도 뒤죽 박죽이다. 하지만 고정인수로 보내는 “서식 문자열”에 포함된 %d, %s, %c 등의 포맷팅 문자들의 개수를 세고, 또 각각의 문자가 받아야 할 인수의 타입을 설명해주므로 적절히 처리할 수 있을 것 같다. 아무튼 가변 인수 함수는 이런 식으로 만들어 진다.