프로그래밍을 하다 보면 함수에 전달되는 인수의 개수가 미리 정해져 있지 않고, 상황에 따라 유동적으로 변해야 하는 경우가 있습니다. 예를 들어 어떤 함수는 인수를 2개 받아야 하고, 또 다른 호출에서는 같은 함수에 5개의 인수를 넘겨야 할 수도 있죠.
C/C++ 언어는 이러한 요구를 해결하기 위해 가변 인수(variable arguments) 기능을 제공합니다. 이를 활용하면 개발자가 필요에 따라 임의 개수의 매개변수를 받을 수 있는 함수를 직접 정의할 수 있습니다.
가변 인수 함수의 기본 구조
가변 인수를 지원하는 함수는 다음과 같은 형태로 선언합니다.
int func(int, ... ) {
.
.
.
}
int main() {
func(1, 2, 3);
func(1, 2, 3, 4);
}여기서 주목해야 할 점은 두 가지입니다.
- 함수
func()의 마지막 매개변수는 생략 부호(ellipsis), 즉 점 세 개(...)로 표현됩니다. - 생략 부호 바로 앞에는 항상 int 타입의 매개변수가 위치하며, 이 값은 실제로 전달된 가변 인수의 총개수를 나타냅니다.
가변 인수 구현 단계
가변 인수 기능을 사용하려면 stdarg.h(C++에서는 cstdarg) 헤더 파일이 필요합니다. 이 헤더 파일은 가변 인수를 처리하기 위한 함수와 매크로를 제공하며, 일반적으로 다음 순서대로 구현합니다.
마지막 매개변수를 생략 부호(
...)로 정의하고, 그 바로 앞에는 인수의 개수를 나타내는 int 매개변수를 배치합니다.함수 내부에 va_list 타입의 변수를 생성합니다. 이 타입은
stdarg.h헤더 파일에 정의되어 있습니다.int 매개변수와 va_start 매크로를 사용하여 va_list 변수를 인수 목록으로 초기화합니다.
va_start매크로 역시stdarg.h에 정의되어 있습니다.va_arg 매크로와 va_list 변수를 이용해 인수 목록의 각 항목에 차례대로 접근합니다.
모든 처리가 끝나면 va_end 매크로를 호출하여 va_list 변수에 할당된 메모리를 정리합니다.
예제 코드: 가변 인수로 평균 구하기
위 단계를 실제로 적용하여, 개수에 상관없이 여러 개의 정수를 전달받아 그 평균을 반환하는 간단한 함수를 작성해 보겠습니다.
#include <iostream>
#include <cstdarg>
using namespace std;
double average(int num,...) {
va_list valist;
double sum = 0.0;
int i;
va_start(valist, num); // num개의 인수를 위해 valist 초기화
for (i = 0; i < num; i++) { // valist에 할당된 모든 인수에 접근
sum += va_arg(valist, int);
}
va_end(valist); // valist를 위해 예약된 메모리 정리
return sum/num;
}
int main() {
cout << "Average of 2, 3, 4, 5 = "<< average(4, 2,3,4,5) << endl;
cout << "Average of 5, 10, 15 = "<< average(3, 5,10,15)<< endl;
}실행 결과
Average of 2, 3, 4, 5 = 3.5 Average of 5, 10, 15 = 10
정리 및 주의 사항
위 예제에서 average() 함수는 첫 번째 인수로 가변 인수의 개수를 받고, 이후 전달된 정수들을 모두 더한 뒤 개수로 나누어 평균을 계산합니다. 이처럼 가변 인수 기능을 사용하면 하나의 함수로 다양한 개수의 입력을 유연하게 처리할 수 있습니다.
다만 몇 가지 주의할 점이 있습니다. 첫째, va_arg 매크로로 값을 읽을 때는 올바른 데이터 타입을 지정해야 하며, 잘못된 타입을 지정하면 정의되지 않은 동작(undefined behavior)이 발생할 수 있습니다. 둘째, 전달된 인수의 개수를 함수 내부에서 알 수 있는 방법이 없기 때문에, 예제처럼 별도의 int 매개변수로 개수를 함께 넘겨주는 것이 일반적입니다. 마지막으로, 현대 C++에서는 타입 안전성이 보장되는 가변 인수 템플릿(variadic templates)이나 initializer_list를 사용하는 것이 권장되므로, 가능하다면 이러한 대안도 함께 고려해 보시기 바랍니다.