[C] 가변 인자
가변 인자 리스트 억세스
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
void va_start( va_list arg_ptr ); // unix version
void va_start( va_list, prev_param ); // ANSI version
parameters
type : 돌려받을 인자의 타입
arg_ptr : 인자리스트의 포인터
prev_param : 첫번째 가변인자 바로 전에 넘겨진 인자 (ANSI only)
Return Value
va_arg는 현재 인자를 리턴한다.
va_start와, va_end는 리턴값이 없다.
Remarks
va_arg, va_end, va_start 매크로는 함수가 가변갯수의 인자를 가질때 함수인자에 접근하는 편리한 방법을 제공한다.
두가지 버젼의 매크로가 사용된다.
STDARG.H 에 정의된 매크로는 ANSI C 표준에서 승인되었고, VARARGS.H에서 정의된 매크로는 UNIX 시스템V정의와 호환된다.
매크로는 다음과 같다.
va_alist : 호출된 함수로의 매개변수 이름( UNIX only )
va_arg : 현재 인자를 되돌려 받는 매크로
va_dcl : va_alist의 선언( Unix only )
va_end : arg_ptr를 리셋하는 매크로
va_list : STDIO.H에 선언된 인자리스트의 포인터
va_start : 가변인자 리스트의 앞부분을 arg_ptr에 설정하는 매크로( Unix only )
매크로 버젼 둘다, 함수에서 필수인자의 고정갯수를 받아들이고, 다음으로 가변갯수의 인자를 받는다고 가정한다.
필수인자는 함수에서 일반적인 매개변수로 선언되고, 파라미터의 이름을 통해서 접근될 수 있다.
가변 인자는 STDARG.H 또는 VARARGS.H에 있는 매크로를 통하여 접근가능하다.
매크로는 인자리스트에 있는 첫번째 가변인자로 포인터를 설정하고, 리스트로부터 인자를 받고, 인자처리가 완료되면 포인터를 리셋한다.
ANSI C표준 매크로는 STDARG.H에 선언되어 있는데, 다음과 같이 사용된다.
함수의 모든 필수인자는 일반적인 방법으로 매개변수로서 선언된다.
va_dcl는 STDARG.H매크로와 같이 사용할 수 없다.
va_start는 함수로 넘겨진 인자리스트의 첫번째 가변인자를 arg_ptr로 설정한다. arg_ptr인자는 va_list 타입이어야 한다.
prev_param 인자는 가변인자의 바로전에 넘겨진(역주 즉 마지막 필수인자) 필수인자의 이름이다.
만약 prev_param이 저장소 클래스로 등록되서 선언되었다면, 매크로의 행동은 정의되지 않는다.
va_start는 va_arg보다 먼저 사용되어야 한다.
va_arg는 arg_ptr에서 주어진 위치에 있는 타입의 값을 돌려받고, 리스트에 있는 다음 인자를 arg_ptr이 가리키도록 증가시킨다.
여기서 다음 인자의 시작부분은 타입의 사이즈를 사용하여 결정한다.
va_arg는 리스트로부터 인자를 얻어낼 수 있다면 함수에서 몇번이라도 호출 할 수 있다.
모든 인자를 얻었다면 va_end로서 포인터를 NULL로 초기화한다.
Unix 시스템 V 매크로는 VARARGS.H에 선언되어 있는데, 다소 다르게 작동한다.
함수의 모든 필수 인자는 일반적인 방법의 매개변수로 선언된다.
함수의 마지막 파라미터가 가변인자리스트를 나타낸다.
이 파라미터는 va_alist로 이름지어져야 한다
(va_list와 혼동하지 말자)
va_dcl은 함수정의 다음의 함수의 중괄호를 열기전에 나타나야 된다.
이 매크로는 va_alist파라미터의 완전한 선언으로서 정의되는데 종결 세미콜론을 포함하고 있다.
따라서 va_dcl다음에 세미콜론은 필요가 없다.
함수 내부에서, va_start는 가변인자 리스트의 처음부분을 arg_ptr로 설정한다.
va_start는 va_arg보다 먼저 사용되어야 한다.
arg_ptr은 va_list 타입이어야 한다.
va_arg는 arg_ptr에서 주어진 위치에 있는 타입의 값을 돌려받고, 리스트에 있는 다음 인자를 arg_ptr이 가리키도록 증가시킨다.
여기서 다음 인자의 시작부분은 타입의 사이즈를 사용하여 결정한다.
va_arg는 리스트로부터 인자를 얻어낼 수 있다면 함수에서 몇번이라도 호출할 수 있다.
모든 인자를 얻었다면 va_end로서 포인터를 NULL로 초기화한다.
/clr ( Common Language Runtime compilation)으로 컴파일 할때, 이들 매크로를 사용한 프로그램은 네이티브와 CLR타입 시스템 간의 차이점 때문에 기대하지 않은 결과를 만들어낸다.
다음 프로그램을 살펴보자.
testit()은 두번째 파라미터로 int나 char*를 요구한다.
그러나 넘겨지는 인자는 0xffffffff( int가 아닌 unsigned int )와 NULL( 실제로는 char*가 아닌 int) 이다.
native코드로 컴파일을 하면 프로그램은 다음과 같이 출력한다.
그러나 /clr:pure로 컴파일을 하면, 타입 불일치가 발생해서 프로그램은 예외를 발생시킨다.
해결책은 명시적인 캐스팅을 하는것이다.
Requirements
* UNIX V호환을 위해 필요
Libraries
C 런타임 라이브러리의 모든 버젼
Example
Output
Average is : 3
Average is : 8
Average is : 0
출처 - 열혈강의 C 포인터 (공동환 저)
변수 n에는 뒤에 나올 인자의 개수를 저장했으므로 n 값에 따라서 인자의 개수를 알 수 있다.
주목할 사항은 첫 번째 매개 변수부터 마지막 매개 변수까지 메모리에 연속적으로 만들어진다는 것이다.
그래서 첫 번째 매개 변수 n의 주소와 크기를 알면 모든 인자의 주소를 알 수 있고 값에 접근할 수 있다.
부가적인 설명을 하자면 C 언어 함수가 함수 호출 규약 중 '__cdecl'이란 방법을 사용하고 '__cdecl'은 함수에 인자를 전달할 때 오른쪽부터 전달한다.
전달된 인자들은 print() 함수의 스택에 차례로 저장된다.
&n이 int형 주소이고 나머지 인자들도 정수(int형)로 전달되므로 int형 포인터 변수를 사용하여 다음과 같이 표현할 수 있다.
ap가 int형 포인터 변수이므로 두 번째 인자의 시작 주소를 저장하여 ap[0], ap[1], ap[2]와 같이 사용하는 것이 가능하다.
위 예제는 for문을 사용하여 간단하게 표현할 수 있다.
위 가변 인자를 쉽게 사용할 수 있도록 매크로 함수를 제공한다.
아래는 앞서 예제를 매크로 함수를 이용하여 구현한 예제이다.
ap는 포인터 변수로 인자의 주소를 저장한다. va_start(ap, n)는 ap 포인터의 위치를 두 번째 인자의 시작 위치로 이동시킨다.
va_arg(ap, int)는 ap가 지시하는 주소의 int형 크기의 메모리 값을 읽고, ap를 그 다음 인자의 시작 주소로 이동시킨다.
va_end(ap)는 ap 포인터에 NULL을 저장한다.
지금까지 가변 인자가 int형(정수)인 경우만을 확인했다. 가변 인자가 어떤 특정 자료형이 아닌 여러 자료형이 전달된다면 첫 번째 인자에 인자의 개수와 자료형의 정보까지도 전달해야 한다.
그래서 printf()와 같은 함수는 첫 번째 인자를 문자열로 만들고 '%c, %d, %s' 등을 사용하여 자료형과 인자의 개수를 전달한다.
'%c'이면 문자를 인자로 전달하고, '%d'이면 전수를 인자로 전달하고, '%s'이면 문자열의 시작 주소를 인자로 전달한다.
아래 예제는 간단한 printf() 함수와 비슷한 기능을 하는 사용자 함수 예제이다.
fmt는 문자열의 시작 주소를 저장하고 ap는 두번째 인자의 주소를 저장하여 minprintf() 함수를 시작한다.
fmt 문자열 중 '%' 문자가 나오면 다음 문자가 'c', 'd', 's'인지를 판단하여 'c' 문자이면 ap 주소를 이용하여 문자를 읽고 'd' 문자이면 ap 주소를 이용하여 정수를 읽고 's' 문자이면 문자열의 주소를 읽는다.
가변 인자 예제 코드 바로가기!!!
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
void va_start( va_list arg_ptr ); // unix version
void va_start( va_list, prev_param ); // ANSI version
parameters
type : 돌려받을 인자의 타입
arg_ptr : 인자리스트의 포인터
prev_param : 첫번째 가변인자 바로 전에 넘겨진 인자 (ANSI only)
Return Value
va_arg는 현재 인자를 리턴한다.
va_start와, va_end는 리턴값이 없다.
Remarks
va_arg, va_end, va_start 매크로는 함수가 가변갯수의 인자를 가질때 함수인자에 접근하는 편리한 방법을 제공한다.
두가지 버젼의 매크로가 사용된다.
STDARG.H 에 정의된 매크로는 ANSI C 표준에서 승인되었고, VARARGS.H에서 정의된 매크로는 UNIX 시스템V정의와 호환된다.
매크로는 다음과 같다.
va_alist : 호출된 함수로의 매개변수 이름( UNIX only )
va_arg : 현재 인자를 되돌려 받는 매크로
va_dcl : va_alist의 선언( Unix only )
va_end : arg_ptr를 리셋하는 매크로
va_list : STDIO.H에 선언된 인자리스트의 포인터
va_start : 가변인자 리스트의 앞부분을 arg_ptr에 설정하는 매크로( Unix only )
매크로 버젼 둘다, 함수에서 필수인자의 고정갯수를 받아들이고, 다음으로 가변갯수의 인자를 받는다고 가정한다.
필수인자는 함수에서 일반적인 매개변수로 선언되고, 파라미터의 이름을 통해서 접근될 수 있다.
가변 인자는 STDARG.H 또는 VARARGS.H에 있는 매크로를 통하여 접근가능하다.
매크로는 인자리스트에 있는 첫번째 가변인자로 포인터를 설정하고, 리스트로부터 인자를 받고, 인자처리가 완료되면 포인터를 리셋한다.
ANSI C표준 매크로는 STDARG.H에 선언되어 있는데, 다음과 같이 사용된다.
함수의 모든 필수인자는 일반적인 방법으로 매개변수로서 선언된다.
va_dcl는 STDARG.H매크로와 같이 사용할 수 없다.
va_start는 함수로 넘겨진 인자리스트의 첫번째 가변인자를 arg_ptr로 설정한다. arg_ptr인자는 va_list 타입이어야 한다.
prev_param 인자는 가변인자의 바로전에 넘겨진(역주 즉 마지막 필수인자) 필수인자의 이름이다.
만약 prev_param이 저장소 클래스로 등록되서 선언되었다면, 매크로의 행동은 정의되지 않는다.
va_start는 va_arg보다 먼저 사용되어야 한다.
va_arg는 arg_ptr에서 주어진 위치에 있는 타입의 값을 돌려받고, 리스트에 있는 다음 인자를 arg_ptr이 가리키도록 증가시킨다.
여기서 다음 인자의 시작부분은 타입의 사이즈를 사용하여 결정한다.
va_arg는 리스트로부터 인자를 얻어낼 수 있다면 함수에서 몇번이라도 호출 할 수 있다.
모든 인자를 얻었다면 va_end로서 포인터를 NULL로 초기화한다.
Unix 시스템 V 매크로는 VARARGS.H에 선언되어 있는데, 다소 다르게 작동한다.
함수의 모든 필수 인자는 일반적인 방법의 매개변수로 선언된다.
함수의 마지막 파라미터가 가변인자리스트를 나타낸다.
이 파라미터는 va_alist로 이름지어져야 한다
(va_list와 혼동하지 말자)
va_dcl은 함수정의 다음의 함수의 중괄호를 열기전에 나타나야 된다.
이 매크로는 va_alist파라미터의 완전한 선언으로서 정의되는데 종결 세미콜론을 포함하고 있다.
따라서 va_dcl다음에 세미콜론은 필요가 없다.
함수 내부에서, va_start는 가변인자 리스트의 처음부분을 arg_ptr로 설정한다.
va_start는 va_arg보다 먼저 사용되어야 한다.
arg_ptr은 va_list 타입이어야 한다.
va_arg는 arg_ptr에서 주어진 위치에 있는 타입의 값을 돌려받고, 리스트에 있는 다음 인자를 arg_ptr이 가리키도록 증가시킨다.
여기서 다음 인자의 시작부분은 타입의 사이즈를 사용하여 결정한다.
va_arg는 리스트로부터 인자를 얻어낼 수 있다면 함수에서 몇번이라도 호출할 수 있다.
모든 인자를 얻었다면 va_end로서 포인터를 NULL로 초기화한다.
/clr ( Common Language Runtime compilation)으로 컴파일 할때, 이들 매크로를 사용한 프로그램은 네이티브와 CLR타입 시스템 간의 차이점 때문에 기대하지 않은 결과를 만들어낸다.
다음 프로그램을 살펴보자.
#include <stdio.h>
#include <stdarg.h>
void testit ( int i, ... )
{
va_list argptr;
va_start( argptr, i );
if( i == 0 )
{
int n = va_arg( argptr, int );
printf( "%d\n", n );
}
else
{
char* s = va_arg( argptr, char* );
printf( "%s\n", s );
}
}
int main()
{
// 첫번째 문제 : 0xFFFFFFFF은 int가 아니다.
testit( 0, 0xFFFFFFFF );
// 두번째 문제 : NULL은 char*가 아니다.
testit( 1, NULL );
}
testit()은 두번째 파라미터로 int나 char*를 요구한다.
그러나 넘겨지는 인자는 0xffffffff( int가 아닌 unsigned int )와 NULL( 실제로는 char*가 아닌 int) 이다.
native코드로 컴파일을 하면 프로그램은 다음과 같이 출력한다.
-1
(null)
그러나 /clr:pure로 컴파일을 하면, 타입 불일치가 발생해서 프로그램은 예외를 발생시킨다.
해결책은 명시적인 캐스팅을 하는것이다.
int main()
{
testit( 0, (int)0xFFFFFFFF );
testit( 1, (char*)NULL );
}
Requirements
| Routine | Required header | Optional headers |
|---|---|---|
| va_arg | <stdio.h> and <stdarg.h> | <varargs.h> * |
| va_end | <stdio.h> and <stdarg.h> | <varargs.h> * |
| va_start | <stdio.h> and <stdarg.h> | <varargs.h> * |
Libraries
C 런타임 라이브러리의 모든 버젼
Example
// crt_va.c
#include <stdio.h>
#define ANSI // UNIX버젼에서는 주석처리하라
#ifdef ANSI // ANSI 호환버젼
#include <stdarg.h>
int average( int first, ... );
#else // UNIX 호환버젼
#include <varargs.h>
int average( va_list );
#endif
int main( void )
{
// 3개의 int를 넣어서 호출 -1은 종결자
printf( "Average is : %d\n", average( 2, 3, 4, -1) );
// 4개의 int를 넣어서 호출 -1은 종결자
printf( "Average is : %d\n", average( 5, 7, 9, 11, -1 ) );
// 단지 -1종결자만으로 호출
printf( "Average is : %d\n", average( - 1 ) );
}
// 정수의 가변인자 리스트로부터 평균값을 리턴
#ifdef ANSI
int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;
va_start( marker, first );
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int );
}
va_end( marker );
return ( sum ? (sum/count ) : 0 );
}
#else
int average( va_alist )
va_dcl
{
int i = 0, count = 0, sum = 0;
va_list marker;
va_start( marker );
for( sum = count = 0; (i = va_arg( marker, int )) != -1; count++ )
{
sum += 1;
}
va_end( marker );
return ( sum ? (sum/count) : 0 );
}
#endif
Output
Average is : 3
Average is : 8
Average is : 0
출처 - 열혈강의 C 포인터 (공동환 저)
#include
void print(int n, ...)
{
if(n == 1)
{
printf("%d\n", *(&n + 1));
}
else if(n == 2)
{
printf("%d %d\n", *(&n + 1), *(&n + 2));
}
else if(n == 3)
{
printf("%d %d %d\n", *(&n + 1), *(&n + 2), *(&n + 3));
}
else
{
puts("인자에 개수가 1~3까지 출력 가능합니다.");
}
}
void main()
{
int a = 10, b = 20, c = 30;
print(1, a);
print(2, a, b);
print(3, a, b, c);
}
변수 n에는 뒤에 나올 인자의 개수를 저장했으므로 n 값에 따라서 인자의 개수를 알 수 있다.
주목할 사항은 첫 번째 매개 변수부터 마지막 매개 변수까지 메모리에 연속적으로 만들어진다는 것이다.
그래서 첫 번째 매개 변수 n의 주소와 크기를 알면 모든 인자의 주소를 알 수 있고 값에 접근할 수 있다.
부가적인 설명을 하자면 C 언어 함수가 함수 호출 규약 중 '__cdecl'이란 방법을 사용하고 '__cdecl'은 함수에 인자를 전달할 때 오른쪽부터 전달한다.
전달된 인자들은 print() 함수의 스택에 차례로 저장된다.
&n이 int형 주소이고 나머지 인자들도 정수(int형)로 전달되므로 int형 포인터 변수를 사용하여 다음과 같이 표현할 수 있다.
#include
void print(int n, ...)
{
int *ap = &n + 1;
if(n == 1)
{
printf("%d\n", ap[0]);
}
else if(n == 2)
{
printf("%d %d\n", ap[0], ap[1]);
}
else if(n == 3)
{
printf("%d %d %d\n", ap[0], ap[1], ap[2]);
}
else
{
puts("인자에 개수가 1~3까지 출력 가능합니다.");
}
}
void main()
{
int a = 10, b = 20, c = 30;
print(1, a);
print(2, a, b);
print(3, a, b, c);
}
ap가 int형 포인터 변수이므로 두 번째 인자의 시작 주소를 저장하여 ap[0], ap[1], ap[2]와 같이 사용하는 것이 가능하다.
위 예제는 for문을 사용하여 간단하게 표현할 수 있다.
#include
void print(int n, ...)
{
int i;
int *ap = &n + 1;
for(i = 0; i < n; i++)
{
printf("%d ", ap[i]);
}
printf("\n");
}
void main()
{
int a = 10, b = 20, c = 30;
print(1, a);
print(2, a, b);
print(3, a, b, c);
}
위 가변 인자를 쉽게 사용할 수 있도록 매크로 함수를 제공한다.
아래는 앞서 예제를 매크로 함수를 이용하여 구현한 예제이다.
#include
#include
void print(int n, ...)
{
int i;
va_list ap;
va_start(ap, n);
for(i = 0; i < n; i++)
{
printf("%d ", va_arg(ap, int));
}
printf("\n");
va_end(ap);
}
void main()
{
int a = 10, b = 20, c = 30;
print(1, a);
print(2, a, b);
print(3, a, b, c);
}
ap는 포인터 변수로 인자의 주소를 저장한다. va_start(ap, n)는 ap 포인터의 위치를 두 번째 인자의 시작 위치로 이동시킨다.
va_arg(ap, int)는 ap가 지시하는 주소의 int형 크기의 메모리 값을 읽고, ap를 그 다음 인자의 시작 주소로 이동시킨다.
va_end(ap)는 ap 포인터에 NULL을 저장한다.
지금까지 가변 인자가 int형(정수)인 경우만을 확인했다. 가변 인자가 어떤 특정 자료형이 아닌 여러 자료형이 전달된다면 첫 번째 인자에 인자의 개수와 자료형의 정보까지도 전달해야 한다.
그래서 printf()와 같은 함수는 첫 번째 인자를 문자열로 만들고 '%c, %d, %s' 등을 사용하여 자료형과 인자의 개수를 전달한다.
'%c'이면 문자를 인자로 전달하고, '%d'이면 전수를 인자로 전달하고, '%s'이면 문자열의 시작 주소를 인자로 전달한다.
아래 예제는 간단한 printf() 함수와 비슷한 기능을 하는 사용자 함수 예제이다.
#include
#include
void minprintf(char *fmt, ...)
{
int i;
va_list ap;
va_start(ap, fmt);
for(i = 0; fmt[i]; i++)
{
if(fmt[i] != '%')
{
putchar(fmt[i]);
}
else
{
switch(fmt[++i])
{
case 'c':
printf("%c", va_arg(ap, char));
break;
case 'd':
printf("%d", va_arg(ap, int));
break;
case 's':
printf("%s", va_arg(ap, char*));
break;
default:
puts("자료형을 잘못 입력 하셨습니다.");
}
}
}
va_end(ap);
}
void main()
{
char c = 'A';
char *str = "ABC";
int a = 10, b = 20;
minprintf("%d + %d = %d\n", a, b, a + b);
minprintf("%s = ABC\n", str);
minprintf("%c%c%c\n", c, c, c);
}
fmt는 문자열의 시작 주소를 저장하고 ap는 두번째 인자의 주소를 저장하여 minprintf() 함수를 시작한다.
fmt 문자열 중 '%' 문자가 나오면 다음 문자가 'c', 'd', 's'인지를 판단하여 'c' 문자이면 ap 주소를 이용하여 문자를 읽고 'd' 문자이면 ap 주소를 이용하여 정수를 읽고 's' 문자이면 문자열의 주소를 읽는다.
가변 인자 예제 코드 바로가기!!!
댓글
댓글 쓰기