59. 함수 가변 인자
1. 키워드
- 가변 인자(Variable Argument)
2. 함수에서 가변 인자 사용하기
- C에서 함수를 사용하다 보면
printf
,scanf
와 같이 매개변수의 개수가 정해지지 않은 함수가 있다. - 이렇게 매번 함수에 들어가는 인수의 개수가 변하는 것을 가변 인자라고 한다.
3. 가변 인자 함수 만들기
- 함수에서 가변 인자를 정의할 때는 고정 매개변수가 한 개 이상 있어야 하며, 고정 매개변수 뒤에
...
을 붙여 매개변수의 개수가 정해지지 않았다는 표시를 해준다. - 단,
...
뒤에는 다른 매개변수를 지정할 수 없다.
#include <stdio.h>
// args는 고정 매개변수
void printNumbers(int args, ...)
{
printf("%d ", args);
}
int main()
{
printNumbers(1, 10);
printNumbers(2, 10, 20);
printNumbers(3, 10, 20, 30);
printNumbers(4, 10, 20, 30, 40);
return 0;
}
// 1 2 3 4
- 함수
printNumbers
를 호출할 때 값을 한 개에서 네 개까지 넣었다. printNumbers(4, 10, 20, 30, 40);
이면 처음에 오는4
는 매개변수args
에 들어가고 나머지 값들은...
부분에 들어간다.
- 이제
...
부분의 매개변수를 사용해 보자.
#include <stdio.h>
#include <stdarg.h> // va_list, va_start, va_arg, va_end가 정의된 헤더 파일
void printNumbers(int args, ...) // 가변 인자의 개수를 받음, ...로 가변 인자 설정
{
va_list ap; // 가변 인자 목록 포인터
va_start(ap, args); // 가변 인자 목록 포인터 설정
for (int i = 0; i < args; i++) // 가변 인자 개수만큼 반복
{
int num = va_arg(ap, int); // int 타입 크기만큼 가변 인자 목록 포인터에서 값을 가져옴
// ap를 int 타입 크기만큼 순방향으로 이동
printf("%d ", num); // 가변 인자 값 출력
}
va_end(ap); // 가변 인자 목록 포인터를 NULL로 초기화
printf("\n"); // 줄바꿈
}
int main()
{
printNumbers(1, 10); // 인수 개수 1개
printNumbers(2, 10, 20); // 인수 개수 2개
printNumbers(3, 10, 20, 30); // 인수 개수 3개
printNumbers(4, 10, 20, 30, 40); // 인수 개수 4개
return 0;
}
// 10
// 10 20
// 10 20 30
// 10 20 30 40
...
으로 들어온 가변 인자를 사용하려면stdarg.h
헤더 파일에 정의된 매크로를 이용해야 한다.- 따라서
#include
로stdarg.h
헤더 파일을 포함해 준다. stdarg.h
에 정의된 가변 인자 처리 매크로는 다음과 같다.
1] va_list
- 가변 인자 목록. 가변 인자의 메모리 주소를 저장하는 포인터이다.
2] va_start
- 가변 인자를 가져올 수 있도록 포인터를 설정한다.
3] va_arg
- 가변 인자 포인터에서 특정 자료형 크기만큼 값을 가져온다.
4] va_end
- 가변 인자 처리가 끝났을 때 포인터를
NULL
로 초기화한다.
- 먼저 함수를 정의할 때
void printNumbers(int args, ...)
와 같이 첫 번째 매개변수에서 가변 인자의 개수를 받을 수 있도록 지정하고, 두 번째 매개변수 부분에서 가변 인자를 받을 수 있도록...
으로 지정한다.
printNumbers
함수를 호출할 때는 다음과 같이 맨 처음에 인수 개수를 넣어준 뒤 인수 개수에 맞게 인수를,
(콤마)로 구분하여 넣어준다.
int main()
{
printNumbers(1, 10); // 인수 개수 1개
printNumbers(2, 10, 20); // 인수 개수 2개
printNumbers(3, 10, 20, 30); // 인수 개수 3개
printNumbers(4, 10, 20, 30, 40); // 인수 개수 4개
return 0;
}
- 다시
printNumbers
함수 안을 살펴보자. - 함수 안에서는
va_start
매크로에 가변 인자 목록 포인터ap
(Argument Pointer)와 가변 인자 개수args
를 넣어 가변 인자를 가져올 수 있도록 준비한다.
- 만약 가변 인자가
4
개 들어있는printNumbers(4, 10, 20, 30, 40);
을 호출한 뒤va_start
매크로를 실행하면 다음과 같은 모양이 된다.
- 이제 반복문으로 가변 인자 개수만큼 반복하면서
va_arg
매크로로 값을 가져오면 된다. - 이때
va_arg
에는 가변 인자의 자료형을 지정해 준다.
for (int i = 0; i < args; i++) // 가변 인자 개수만큼 반복
{
int num = va_arg(ap, int); // int 타입 크기만큼 가변 인자 목록 포인터에서 값을 가져옴
// ap를 int 타입 크기만큼 순방향으로 이동
printf("%d ", num); // 가변 인자 값 출력
}
int num = va_arg(ap, int);
를 실행하면 현재ap
에서 4바이트(int
타입 크기)만큼 역참조하여 값을 가져온 뒤ap
를 4바이트만큼 순방향으로 이동시킨다.
- 즉, 반복문에서 반복할 때마다
ap
는 4바이트만큼 순방향으로 이동하므로10
,20
,30
,40
을 순서대로 가져올 수 있다.
- 마지막으로
va_end
매크로를 사용하여ap
를NULL
로 초기화한다.
- 인텔/AMD x86, x86-64 플랫폼에서는
va_end
매크로를 사용하지 않아도 동작에 지장이 없다. - 하지만 다른 플랫폼에서는 문제가 생길 수도 있으므로 호환성을 위해서
va_end
로 마무리를 해주는 것이 좋다.
stdarg.h
와 varargs.h
varargs.h
는 오래된 컴파일러에서 제공하는 헤더 파일이며,stdarg.h
는 C89 표준부터 추가된 헤더 파일이다.varargs.h
도stdarg.h
와 사용법이 거의 비슷하다.
4. 자료형이 다른 가변 인자 함수 만들기
- 앞에서는 가변 인자의 자료형이 모두
int
타입이었다. - 자료형이 다른 가변 인자를 처리할 때는
switch
와 가변 인자를 함께 사용하면 된다.
#include <stdio.h>
#include <stdarg.h> // va_list, va_start, va_arg, va_end가 정의된 헤더 파일
void printValues(char *types, ...) // 가변 인자의 자료형을 받음, ...로 가변 인자 설정
{
va_list ap; // 가변 인자 목록
int i = 0;
va_start(ap, types); // types 문자열에서 문자 개수를 구해서 가변 인자 포인터 설정
while (types[i] != '\0') // 가변 인자 자료형이 없을 때까지 반복
{
switch (types[i]) // 가변 인자 자료형으로 분기
{
case 'i': // int 타입일 때
printf("%d ", va_arg(ap, int)); // int 타입 크기만큼 값을 가져옴
// ap를 int 타입 크기만큼 순방향으로 이동
break;
case 'd': // double 타입일 때
printf("%f ", va_arg(ap, double)); // double 타입 크기만큼 값을 가져옴
// ap를 double 타입 크기만큼 순방향으로 이동
break;
case 'c': // char 타입 문자일 때
printf("%c ", va_arg(ap, char)); // char 타입 크기만큼 값을 가져옴
// ap를 char 타입 크기만큼 순방향으로 이동
break;
case 's': // char 타입 포인터 문자열일 때
printf("%s ", va_arg(ap, char *)); // char 타입 포인터 크기만큼 값을 가져옴
// ap를 char 타입 포인터 크기만큼 순방향으로 이동
break;
default:
break;
}
i++;
}
va_end(ap); // 가변 인자 포인터를 NULL로 초기화
printf("\n"); // 줄바꿈
}
int main()
{
printValues("i", 10); // 정수
printValues("ci", 'a', 10); // 문자, 정수
printValues("dci", 1.234567, 'a', 10); // 실수, 문자, 정수
printValues("sicd", "Hello, world!", 10, 'a', 1.234567); // 문자열, 정수, 문자, 실수
return 0;
}
// 10
// a 10
// 1.234567 a 10
// Hello, world! 10 a 1.234567
- 먼저
printValues
함수를 만들 때 가변 인자의 자료형types
는 문자열 포인터로 지정해 주고, 그 뒤에는...
를 사용하여 가변 인자로 지정해 준다. - 여기서 가변 인자의 자료형은
"i"
,"dci"
처럼 약칭을 조합한 문자열로 받는다.
printValues
함수를 호출할 때는 다음과 같이 맨 처음에 가변 인자 자료형의 약칭을 순서대로 넣어준다.- 그리고 자료형 약칭의 개수에 맞게 인수를
,
로 구분하여 넣어주면 된다.
int main()
{
printValues("i", 10); // 정수
printValues("ci", 'a', 10); // 문자, 정수
printValues("dci", 1.234567, 'a', 10); // 실수, 문자, 정수
printValues("sicd", "Hello, world!", 10, 'a', 1.234567); // 문자열, 정수, 문자, 실수
return 0;
}
- 다시
printValues
함수 안을 살펴보자. - 함수 안에서는
va_start
매크로에 가변 인자 목록 포인터ap
와 가변 인자 자료형 문자열types
를 넣는다. va_start
매크로는 문자열을 넣으면 문자의 개수를 구해서 포인터를 설정해 준다.
- 만약 가변 인자로 문자열, 정수, 문자, 실수가 들어있는
printValues("sicd", "Hello, world!", 10, 'a', 1.234567);
을 호출한 뒤va_start
매크로를 실행하면 다음과 같은 모양이 된다. "Hello world!"
는 문자열 리터럴이기 때문에 문자열이 그대로 들어가지 않고 문자열의 주소가 들어간다.
while
반복문을 사용하여types
의 문자가'\0'
(NULL
)이 아닐 때까지 반복한다.types
는 문자열이므로 반드시NULL
로 끝난다.- 그리고
switch
분기문을 사용하여types
에 들어있는 문자로 분기한 뒤va_arg
로ap
에서 자료형 크기만큼 값을 가져온다. - 예를 들어
types[i]
가'd'
이면double
타입 크기만큼 값을 가져온다.
while (types[i] != '\0') // 가변 인자 자료형이 없을 때까지 반복
{
switch (types[i]) // 가변 인자 자료형으로 분기
{
case 'i': // int 타입일 때
printf("%d ", va_arg(ap, int)); // int 타입 크기만큼 값을 가져옴
// ap를 int 타입 크기만큼 순방향으로 이동
break;
case 'd': // double 타입일 때
printf("%f ", va_arg(ap, double)); // double 타입 크기만큼 값을 가져옴
// ap를 double 타입 크기만큼 순방향으로 이동
break;
case 'c': // char 타입 문자일 때
printf("%c ", va_arg(ap, char)); // char 타입 크기만큼 값을 가져옴
// ap를 char 타입 크기만큼 순방향으로 이동
break;
case 's': // char 타입 포인터 문자열일 때
printf("%s ", va_arg(ap, char *)); // char 타입 포인터 크기만큼 값을 가져옴
// ap를 char 타입 포인터 크기만큼 순방향으로 이동
break;
default:
break;
}
i++;
}
va_end(ap); // 가변 인자 포인터를 NULL로 초기화
- 즉, 반복문에서 반복할 때마다
ap
는"sicd"
순서대로char
타입 포인터,int
,char
,double
타입 크기만큼 순방향으로 이동하므로"Hello, world!"
,10
,'a'
,1.234567
을 가져올 수 있다.
GCC에서 va_arg
va_arg
매크로는 Visual Studio와 GCC에서 허용하는 자료형이 조금 다르다.- 먼저 Visual Studio에서는
va_arg(ap, char)
처럼char
타입을 사용할 수 있다.
- 하지만, GCC에서는
char
타입 문자일 때va_arg
매크로에char
타입 대신int
타입을 사용해야 한다.
- 즉, GCC에서 가변 인자로 받은 값의 자료형이
int
타입보다 작다면int
타입으로,float
타입이라면double
타입으로 지정해야 한다.
1] char
, bool
타입 → int
타입
2] short
타입 → int
타입
3] float
타입 → double
타입
- GCC 쪽이 C 표준이며, Visual Studio에서는 예외적으로 허용하고 있는 상황이다.