관리 메뉴

드럼치는 프로그래머

[C/C++] va_arg, va_start, va_end 본문

★─Programing/☆─C | C++

[C/C++] va_arg, va_start, va_end

드럼치는한동이 2008. 5. 13. 20:35

va_arg, va_start, va_end

가변 매개 변수를 처리합니다.

Declaration

type va_arg( va_list arg_ptr, type )
void va_start( va_list arg_ptr, prev_param )
void va_end( va_list arg_ptr )

Return value

성공 - va_arg()는 현재 매개 변수 반환
       va_start()와 va_end()는 반환값 없음
실패 - 없음


Parameters

type       - 읽어올 매개 변수 자료형
arg_ptr    - 매개 변수 목록을 가리키는 포인터
prev_param - 가변 매개 변수의 바로 앞에 전달된 변수


Detail descriptions

va_arg(), va_start(), va_end()는 함수에 전달되는 매개 변수를 가변적으로 처리하기 위한 매크로(macro) 함수입니다. 여기서 "가변"이란 매개 변수의 개수가 일반 함수들처럼 고정되지 않았음을 의미합니다. 가변 매개 변수를 사용하는 대표 함수로 printf()와 scanf()가 있습니다. 이들 함수는 필요에 따라 원하는 만큼의 변수를 화면에 출력할 수 있습니다. 매크로 함수는 define문으로 정의한 코드로, 함수는 아니지만 함수처럼 사용하기 때문에 코드만 봐서는 진짜 함수인지 매크로 함수인지 구별할 수 없습니다.

 

va_arg()들이 매개 변수의 개수를 원하는 대로 설정할 수 있게 도와준다고 해서, 무턱대고 매개 변수를 전달할 수는 없습니다. 매개 변수는 원래 함수 스스로 처리할 수 있는 구조로 되어있지만, 개수가 바뀔 수 있다면 인위적으로 매개 변수 전달에 사용하는 스택(stack)에 접근할 수밖에 없습니다. 따라서 호출된 함수 내부에서는 몇 개의 매개 변수를 전달받았는지 파악할 수 있는 방법이 필요해집니다. 가장 쉬운 방법으로 몇 개가 전달되었는지 알려주는 방법이 있습니다.

매개 변수 개수를 파악하기 위해 최소 한 개의 매개 변수는 있어야 합니다. 어떻게 알려줄지는 프로그래머가 결정하겠지만, 첫 번째로 전달된 매개 변수를 통해 개수를 파악할 수 있어야 할 것입니다. printf()는 매개 변수의 개수를 문자열로 전달하고, 처리할 변수의 자료형은 타입 문자(%c, %d, ...)로 판단합니다. printf()의 최소 매개 변수 개수는 한 개이고, 첫 번째 매개 변수는 반드시 문자열이어야 합니다. 참고로 개수에 대한 단서를 갖고 있는 매개 변수는, 가변적으로 전달되는 매개 변수의 바로 앞에 나와야 합니다.

가변 매개 변수를 사용하기 위한 순서는 다음과 같습니다.

  1. 스택을 가리키는 매개 변수 포인터 변수를 선언합니다. 자료형은 va_list입니다.
     
        va_list marker;
     
  2. 포인터 변수를 가변 매개 변수의 첫 번째 위치로 설정합니다. count는 매개 변수의 개수에 대한 단서를 갖는 필수 매개 변수로, 가변 매개 변수의 바로 앞에 오는 변수입니다.
     
        va_start( marker, count );
     
  3. 포인터가 가리키는 위치에서 매개 변수를 읽어오고, 다음으로 이동합니다. 이때 두 번째 매개 변수로는 어느 정도 이동을 해야 되는지 알려주는 자료형을 전달합니다. 이 작업은 매개 변수를 모두 처리할 때까지 반복합니다.
     
        va_arg( marker, int );
     
  4. 사용이 끝난 매개 변수 포인터를 초기화합니다.
     
        va_end( marker );

가변 매개 변수를 받는 함수를 호출할 때는 다른 함수들처럼 호출하면 됩니다. 다만 가변 매개 변수들이 매개 변수 목록의 마지막에 오도록 주의해야 합니다. 가변 매개 변수가 들어가는 함수 선언 부분에는 몇 개가 올지 모르기 때문에 ...으로 표현합니다. 다음은 printf()의 선언으로 마지막에 ...이 있는 것을 볼 수 있습니다. []는 있을 수도 있고 없을 수도 있는 옵션을 뜻합니다. 따라서 printf()의 최소 매개 변수는 format 매개 변수 한 개입니다.

    int printf( const char *format [, argument]... );

va_start()는 전달된 매개 변수 목록의 첫 번째 변수 위치로 포인터를 이동시킵니다. 이동시킬 포인터 변수는 반드시 va_list 자료형이어야 합니다. va_start()는 포인터 위치를 설정하기 위해 처음에 한번만 호출합니다. va_start()의 두 번째 매개 변수는 반드시 가변 매개 변수의 바로 앞에 오는 변수여야 합니다. va_start() 호출 없이 va_arg()를 사용하면 어떤 결과가 나올지 알 수 없습니다. 호출 순서는 반드시 지켜져야 합니다.

va_arg()는 포인터가 가리키는 곳의 매개 변수를 반환합니다. 그런 다음 포인터를 다음 번 매개 변수 위치로 이동시킵니다. va_arg()가 매개 변수를 정확하게 반환하기 위해서는 처리할 변수의 자료형에 대해 알고 있어야 합니다. 자신의 두 번째 변수로 자료형을 받는 이유입니다. va_arg()는 매개 변수를 모두 읽을 때까지 반복적으로 사용해야 하므로 보통 반복문 안에서 사용합니다.

va_end()는 가변 매개 변수를 모두 처리하고 나서, 마지막에 한 번 호출합니다. 사용이 끝난 포인터를 NULL 포인터로 초기화하는 역할을 맡습니다. 함수가 종료하기 전에 va_end()를 호출하지 않았을 때의 결과는 정의되지 않았기 때문에 반드시 한 번은 호출해야 합니다.


Remarks

C 언어 표준에는 여기서 설명한 매크로 함수외에 va_list 변수를 복사하는 va_copy()도 있습니다. va_copy()는 매개 변수를 처리하는 도중에 현재 위치를 저장하기 위해 사용합니다. 현재 위치를 저장해서 다시 한번 처리를 하거나 아니면 가변 매개 변수를 여러 개로 분리해서 처리하는 등의 용도로 사용합니다. 그러나, VC++ 컴파일러는 va_copy()의 정의를 제공하지 않습니다.


Header files

<stdarg.h>


Example codes

  1. 가장 쉬운 형태의 가변 매개 변수 처리 방법을 보여줍니다. 모든 가변 매개 변수는 int 자료형으로 가정합니다.
  2. Summation()는 첫 번째 매개 변수로 가변 매개 변수의 개수를 요구합니다. 여기서는 Summation() 호출시에 세 개(n, m, k)를 전달합니다.
  3. Summation()의 내부 반복문을 보면, int 자료형만 다루기 때문에 코드가 간결하다는 것을 알 수 있습니다.
  4. Summation()를 사용할 때 주의할 점은 int 자료형만 다루기 때문에 다른 자료형을 전달해서는 안된다는 점입니다. 분명 알 수 없는 결과, 다시 말해 정의되지 않은 결과를 반환할 것입니다.

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

int Summation( int count, ... );

void main()
{
    int n, m, k, sum;

    printf( "입력 : " );
    scanf( "%d %d %d", &n, &m, &k );

    sum = Summation( 3, n, m, k );
    printf( "합계 : %d\n", sum );
}

int Summation( int count, ... )
{
    int i, sum = 0;
    va_list marker;

    va_start( marker, count );
    for( i = 0; i < count; i++ )
        sum += va_arg( marker, int );

    va_end( marker );

    return sum;
}

[출력 결과]
입력 : 20 5 11
합계 : 36


  1. 가변 매개 변수를 사용하는 가장 일반적인 형태의 코드를 보여줍니다. printf()의 기능 중에 char, int, double, char* 자료형만 다룹니다.
  2. MiniPrintf()를 사용할 때 주의할 점은 %와 타입 문자 사이에 공백이 없어야 합니다. 또한 각각의 형식(%c, %s, ...) 사이에도 공백이 없어야 합니다.
  3. 처리할 자료형이 많아져서 반복문이 복잡해졌습니다. 각각의 자료형을 정확하게 전달해서 포인터를 다음 매개 변수로 이동시킵니다. 변수 i는 단지 몇 번째 매개 변수인지를 나타냅니다.
  4. 반복문 처음에 나오는 str++는 % 문자를 처리하고, 마지막에 나오는 str++는 타입 문자(c, d, f, s)를 처리합니다.

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

void MiniPrintf( char* str, ... );

void main()
{
    char   ch;
    int    n;
    double dbl;
    char   str[32];

    printf( "문자 - 정수 - 실수 - 문자열 : " );
    scanf( "%c %d %lf %s", &ch, &n, &dbl, str );
    MiniPrintf( "%c%d%f%s", ch, n, dbl, str );
}

void MiniPrintf( char* str, ... )
{
    int i;
    va_list marker;

    va_start( marker, str );

    for( i = 0; *str != '\0'; i++ )
    {
        assert( *str == '%' );

        str++;

        switch( *str )
        {
        case 'c' : printf( "%d : %c\n", i, va_arg(marker, char  ) );   break;
        case 'd' : printf( "%d : %d\n", i, va_arg(marker, int   ) );   break;
        case 'f' : printf( "%d : %f\n", i, va_arg(marker, double) );   break;
        case 's' : printf( "%d : %s\n", i, va_arg(marker, char* ) );   break;
        }

        str++;
    }

    va_end( marker );
}

[출력 결과]
문자 - 정수 - 실수 - 문자열 : R 123 3.14 raindrops
0 : R
1 : 123
2 : 3.140000
3 : raindrops
이 문서에 대한 모든 권리는 www.printf.co.kr에 있습니다. [최종 수정일: 2006.12.30.]
Comments