본문 바로가기
C,C++/C++

[C++] 가변 인자(Variable argument) 사용법을 알아보자

by woohyeon 2019. 12. 15.
반응형

가변 인자(Variable argument)란 말 그대로 고정되지 않은 인자를 말한다. C언어의 printf() 함수처럼 정해지지 않은 개수의 인자를 받아야 할 때 사용한다. 가변 인자는 아래와 같이 인자를 ... 표시로 나타낸다.

void func(int ...) { }

위 표현은 int 타입의 매개변수를 사용자가 입력한 만큼 받겠다는 의미이다.

따라서 함수 호출 시 아래와 같이 사용할 수 있다.

func(1, 3, 2, 5, 8);
func(1, 2);
func(4);
func(5, 1, 4, 8);
...

 

함수 내에서 가변 인자를 다루기 위해 사용하는 매크로 4가지가 있는데, 우선 다음 헤더 파일을 포함시켜야 한다.

#include <cstdarg>



헤더엔 다음과 같은 매크로가 정의되어 있다.



매크로 사용하기 위해선 va_list 라는 타입의 변수를 선언해야 하며, 우리가 지정한 n개의 가변 인자의 시작 주소를 저장하는 포인터가 된다. 

 

va_start 매크로를 통해 va_list가 어디를 가리켜야 하는지 설정한다. 

va_start(va_list ap, T prev);

 

예를 들어 다음의 함수에서 가변인자는 _count 다음부터 시작되므로, va_start 매크로의 두 번째인자로 _count를 전달해준다.

void func(int _count, int ...)
{
   va_list list;
   
   va_start(list, _count); // list는 _count의 다음 인자를 가리키게 된다.
}

 


va_arg는 현재 list가 가리키는 곳의 값을 반환하고, 두 번째 인자 타입의 크기만큼 주소를 증가시킨다.
즉 가변인자 리스트에서 현재 인자를 반환하고 그 다음 인자로 넘어가기 위해 사용한다.

va_arg(list, int);


va_end는 va_list 타입의 변수를 받으며 list를 초기화 전의 상태로 만든다.
list 사용이 끝나면 end를 통해 초기화 전 상태로 만들어 준다.

va_copy는 va_list 타입의 변수 2개를 인자로 받으며 첫 번째 list에 두 번째 list를 복사한다.
즉 list2가 0x0004를 가리킬 경우 va_copy(list1, list2) 호출 시 list1이 0x0004를 가리키게 된다.
va_copy의 첫 인자는 초기화 상태가 아니어도 된다.


다음은 가변 인자의 개수에 대해 생각해보자. 일단 전달받은 가변 인자의 개수를 반환하는 매크로는 따로 정의되어 있지 않다.
따라서 따로 방법을 취해주지 않으면 반복문을 사용할 수 없다.

반복문을 사용할 수 있는 방법은 대표적으로 2가지가 있다.

첫 번째 방법은 첫 인자로 개수를 전달하고 두 번째 인자부터 가변 인자를 받는 것이다.
두 번째는 마지막 인자에 절대 들어올 수 없는 값(ex: NULL, -1..)을 넣어서 while문을 통해 반복하는 것이다.

간단한 사용 예시로 알아보자.

1. 첫 인자로 개수를 전달

#include <cstdarg>
#include <iostream>

template <typename _Ty>
void func(int _count, _Ty ...) // 첫 인자로 개수를 전달
{
   assert(_count >= 0);
   va_list list;
   va_list copy;
   
   va_start(list, _count); // list는 _count의 다음 인자를 가리키게 된다.
   va_copy(copy, list);    // copy는 list가 가리키는 주소를 가리키게 된다.
   
   for(int i = 0; i != _count; ++i)
     std::cout << va_arg(copy, _Ty) << " ";
     
   va_end(list);
   va_end(copy);
}

void main()
{
  int n = 5;
  func(n, 1, 23, 4, 8 ,31); // 1 23 4 8 31
}

 


2. 마지막 인자로 NULL과 같은 값을 전달

#include <cstdarg>
#include <iostream>

template <typename _Ty>
void func(_Ty first, _Ty ...) // 마지막 인자로 NULL과 같은 값을 전달
{
   va_list list;
   
   va_start(list, first); // list는 first의 다음 인자를 가리키게 된다.
   
   _Ty val = first;
   
   std::cout << first << " "; // list는 first의 다음 인자를 가리키기에 first는 따로 출력해주어야 한다.  
   while(val != NULL)
   {
     std::cout << (val = va_arg(list, _Ty)) << " \n";
   }
         
   va_end(list);
}

void main()
{
  func('a', 'c', 'g', 'x', '\0'); // a c g x
}


상황에 맞게 사용하는 것이 좋지만 첫 번째 방법이 간편하다.

또한 위에서 본 두 예시에선 템플릿을 이용하여 va_arg의 두번째 인자로 타입 매개변수를 전달하였는데
이는 단순한 출력 예시이기 때문에 템플릿을 통해 예시를 작성하였고, 연산이나 다른 로직이 필요하다면 템플릿을 사용하지 않거나 알맞게 사용해야 한다.

참고로 템플릿의 타입 매개변수 또한 가변 인자를 사용할 수 있으며 이에 대해선 나중에 따로 작성해야 할 듯하다




댓글