본문 바로가기

C/C++

[스크랩] C언어 가변인자(가변파라미터)를 사용해보자

출처 : http://norus.tistory.com/19#comment12673780


printf의 원형을 찾아보신 분이 계실지 모르겠지만, printf의 원형을 찾아보면 다음과 같습니다.


int Printf (const char* Format, ...)

(실제 정의되어 있는 부분과 약간 차이가 있을 수 있습니다.)



위에 보이시는 "..." 이 부분이 가변 인자 혹은 가변 파라미터라고 불리우는 것입니다.


printf를 쓸 때 느끼셨겠지만, 파라미터로 아무것도 넘겨주지 않을 수도 있고, 여러개를 넘겨줄 수도 있습니다.




가변 인자가 무엇인지 알았으니 이제 차근차근 설명해 드리도록 하겠습니다.



우선, 가변 인자 함수를 만들기 위해서는 stdarg.h 헤더가 필요합니다. 이 헤더에 가변인자함수를 만들 때 필요한 매크로 들이 정의되어 있습니다.


그리고 최소 1개 이상의 고정 인수가 있어야 합니다. 가변인자를 나타내는 "..." 은 파라미터 순서 상 가장 뒤에 있어야 합니다.




말로만 설명하면 헷갈리실테니 먼저 소스코드 예제를 보여드리겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdarg.h>
 
int sum(int count, ...)
{
    int res = 0;
    va_list ap;
    int i;
 
    va_start(ap, count);
     
    for(i=0; i>count; i++)
        res += va_arg(ap, int);
 
    va_end(ap);
 
    return res;
}
 
int main()
{
    printf("%d\n", sum(10, 1,2,3,4,5,6,7,8,9,10));
 
    return 0;
}

<출력 결과>

55



위 예제는 가변인자함수를 이용해서 모든 파라미터를 더해주는 Sum 함수를 만든 것입니다.


자 그럼 지금부터 가변인자함수에 필요한 애들을 소개하겠습니다.



  • va_list : 모양은 멋지게 생겼지만 사실은 char * 로 정의되어 있는 녀석입니다. 이 포인터가 각 인자의 시작주소를 가리키게 됩니다.
  • va_start : 요놈이 va_list로 만들어진 포인터에게 가변인자 중 첫 번째 인자의 주소를 가르쳐주는 중요한 매크로입니다.
    이 녀석의 모양은 사실 이렇게 생겼습니다.

    #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

    매크로의 첫 번째 인자로는 va_list로 만든 포인터가 담기고,
    매크로의 두 번째 인자로는 마지막 고정 인수가 담겨야 합니다.

    * _ADDRESSOF(v) => &(v) , 즉 주소로 바꿔주는 매크로입니다.
    * _INTSIZEOF(n)   => ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) , 비트 연산이 들어가는데 자세한 계산까지는 알 필요 없을 것 같고 마지막 고정인수의 size를 구해서 그 다음 인자의 시작주소. 즉 가변인자의 시작주소까지의 메모리상의 "거리" 를 구해주는 매크로입니다.

  • va_arg : 얘는 특정 가변인자를 가리키고 있는 va_list의 포인터를 다음 가변인자로 이동시켜 주는 매크로입니다.
    이 녀석의 모양은 아래와 같습니다.

    #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

    매크로의 첫 번째 인자로는 va_list로 만든 포인터가 담기고,
    매크로의 두 번째 인자로는 타입 이름이 담깁니다. (int, long, double 등등) 

    * 참고: char, short 의 경우에는 int로 대신 쓰고, flaot의 경우에는 double로 대신 쓴 이후 형 변환을 해주어야 한다고 합니다. (예. char ch = (char) va_arg(ap, int); )

  • va_end : 얘는 가변인자를 끝내는 역할입니다. 단순히 모양을 보면 NULL 포인터로 돌려주는 매크로인데, 프로그램상 어떤 경우가 생길 지 모르니까 놓치지 말고 써주도록 합시다!

    #define _crt_va_end(ap)      ( ap = (va_list)0 )

    매크로의 인자로 va_list로 만든 포인터가 담깁니다.



그리고 가변인자함수를 사용할 때, 또 많이 사용하는 함수가 하나 있는데요.


int vsprintf (char* Dest, const char* Format, va_list Args)

int vsnprintf (char* Dest, size_t MaxCount, const char* Format, va_list Args)

  •  함수명    : vsprintf
  •  필요헤더 : stdio.h
  •  리턴타입 : int
  •  파라미터 : 1. Format에 따라 만들어진 내용이 담길 버퍼,
                    2. 포멧
                    3. 가변 파라미터
  •  리턴값    : 문자열의 길이
  •  함수역할 : Dest 변수에 형식에 따라 만들어진 문자열이 저장된다.


바로 요놈입니다!


sprintf와 상당히 비슷하게 생긴 이놈,


이 놈은 다양한 타입의 가변 인자들을 %d, %s, %c 등의 형식을 읽어서 알아서 예쁘게 포장해주는 함수입니다.


우리가 %d 를 발견했을 때는 타입이 int 형이므로 다음 가변 인자를 va_arg(ap, int) 로 받아와서 담고, %s를 발견했을 때는 char * 형이므로 va_arg........ 


물론 이렇게 직접 만들어주어도 되지만!! 자동으로 포장해주는 함수가 만들어져 있잖아요! 그럼 우린 얘를 쓰면 되는겁니다. ㅎㅎ



어디다 쓰는지는 아래 예제를 보도록 하겠습니다. 제가 가변인자를 사용하는 90%는 아래와 같은 목적때문입니다!



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
 
void Err_print(char* fmt, ...)
{
    char buf[512] = {0,};
    va_list ap;
 
    strcpy (buf, "Error: ");
    va_start(ap, fmt);
    vsprintf(buf + strlen(buf), fmt, ap);
    va_end(ap);
 
    puts(buf);
}
 
int main()
{
    int a = 10, b = 0;
 
    if(b != 0)
        printf("%d\n", a / b);
    else
        Err_print("Don't divide by %d\n", b);
 
    return 0;
}

<출력결과>


Error: Don't divide by 0



아주 간단하죠? 위와 같이 Error 처리용 함수로 만들어 사용할 수 있습니다.


이름도 Error_printf 이런식으로 바꿔서 사용하게 됨으로써, 프로그램 코드를 볼 때 "아! 이 부분은 에러처리부분!" 하며 넘어갈 수 있는 등, 코드 가시성이 높아지구요.


프로그램 동작 중 발생하는 error에 대해 errno로 저장하는 프로그래밍이 되어 있다면 어떤 에러가 발생했는지 앞, 또는 뒤에 덧붙여서 출력해 줄 수도 있습니다. (물론 출력이 아니고 fprintf 등을 이용하여 프로그램 에러로그 안에 담을 수도 있습니다.)




아무튼 이렇게 가변인자에 대한 사용을 알아보았습니다!


이상으로 오늘의 포스팅을 마치도록 하겠습니다.




* 원래 매개변수와 인자(parameter, argument) 등의 용어는 명백히 구별되지만, 본문에서는 거의 같은 의미로 놓고 사용 되었기에 용어가 왔다갔다 할 수 있습니다. 두 용어의 의미 상 구별을 하지 않았다는 점, 다시 한 번 말씀드립니다.