본문 바로가기
프로그래밍/C || CPP

C/C++ 가변인자 (variable argument)

by _BlankSpace 2017. 5. 21.


코드는 https://github.com/blankspace-dev 참고 부탁드립니다.


  1. 가변인자란?

이번 포스팅은 가변인자에 대해서 정리하고자 합니다. 가변인자는 말 그대로 변수의 개수가 그때그때 변할 수 있는 인자라고 할 수 있습니다. 예를 들면, prinft 와 scanf 를 기본적으로 들 수 있겠습니다.


!!???? printf와 scanf (또는 cout, cin) 가 그렇게 거창한 이름을 가질만한 함수였어? 라고 생각하시는 분도 계실 수 있습니다. 하지만 생각해보면 print 와 scanf는 굉장히 사용자를 편하게 해주는 함수이라는 것이라는 사실에 동감하실 겁니다.


1
2
3
printf"%s %s %d %d %u %p ...", ~~, ~~ ,~~ ,~~ ,~~ ,~~...);
 
scanf"%s %d %s %c %s ...", ~~, ~~, ~~, ...);
cs

이러한 printf와 scanf는 사용자의 임의대로 출력이나 입력하고자 하는 만큼 넣을 수 있습니다. 내부의 과정은 생각하지 않아도 된다는 말입니다.

그렇지만, 초보 개발자의 입장에선 이러한 동작 방법에 의문이 들 것입니다. 내가 지금까지 구현한 함수는 모두 인자의 개수가 정해진 것인데 어떻게 저런 방식이 가능하다는 것일까.. 라는 의문 말입니다.


따라서 C/C++에서 가변인자를 가지는 함수를 정의하는 법을 정리하고자 합니다.


VariableEx1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
 
using namespace std;
 
void PrintNums(int args, ...)
{
    cout << "args : " << args << endl;
}
 
int main(void)
{
    PrintNums(11);
    PrintNums(212);
    PrintNums(3123);
    PrintNums(41234);
    return 0;
}
cs

결과:

 >> args : 1

 >> args : 2

 >> args : 3

 >> args : 4 

위의 예제가 컴파일 에러가 나지 않는 것은 5번 라인의 ... 때문입니다. ...은 가변 인자를 가져오기 위한 용도라고 생각하면 됩니다. 따라서 12 ~ 15 라인의 내용을 보면 보내는 인자의 개수는 다르지만, 에러가 나지 않는 것입니다.


하지만, 결과는 위와 같이 첫 번째 인자만 가져올 수 밖에 없습니다. 이러한 인자의 개수가 다를 때는 다음과 같은 방법을 사용해야 합니다.




 2. 가변 인자를 사용하는 방법. (1)

먼저, 가변 인자를 사용하기 위해서는 다음의 함수 용도를 이해해야합니다.


- va_start(va_list, lastfix)

스택 상의 첫 가변인수의 위치를 구해서 ap에 대입합니다. ap는 void형 포인터 변수(va_list 형)이며, 함수가 호출되기 전에 선언되어 있어야 합니다.

두 번째 인수 lastfix는 고정 인수를 뜻합니다. 고정 인수가 필요한 이유는 가변인수의 시작번지를 찾기 위해서 중요한 역할을 합니다. 그 다음의 번지가 가변인수의 시작번지이기 때문입니다.


- va_arg(va_list, type)

가변인수를 읽는 함수라고 생각하면 되겠습니다. 첫 번째 인수 ap는 va_start가 사용한 ap와 동일한 변수이고, ap가 가리키는 번지의 데이터를 가변인수로 읽습니다.

두 번째 인수 type은 가변인수의 데이터형을 나타냅니다. 그러므로 데이터형에 따라서 데이터를 읽어 리턴한다고 생각하면 됩니다.

type의 인수로는 char, float은 사용될 수 없다고 합니다. 이것의 대체 방법은 밑에서 설명하겠습니다.

(window와 linux는 다르므로, 환경에 따라 허용될 수도 있습니다. 이것은 밑에서 설명하겠습니다.)


- va_end(va_lis)

가변 인수를 다 읽은 후, 함수가 정상적으로 리턴되도록 도와줍니다. va_end가 호출되면, ap가 변경되므로 va_satrt를 호출하지 않고는 가변인수를 다시 읽을 수 없도록 합니다.


예제를 바탕으로 설명하겠습니다.


VariableEx2.cpp

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
#include <iostream>
#include <cstdarg>  // c 에서는 stdarg.h
 
using namespace std;
 
void PrintNums(int args, ...)
{
    va_list ap;
 
    va_start(ap, args);
    for (int i = 0; i < args; i++) {
    int n = va_arg(ap, int);
    cout << "args " << args << " : " << n << endl;
    }
    va_end(ap);
    cout << endl;
}
 
int main(void)
{
    PrintNums(11);
    PrintNums(212);
    PrintNums(3123);
    PrintNums(41234);
    return 0;
}
cs


결과:

 >> args 1 : 1


 >> args 2 : 1

 >> args 2 : 2


 >> args 3 : 1

 >> args 3 : 2

 >> args 3 : 3


 >> args 4 : 1

 >> args 4 : 2

 >> args 4 : 3

 >> args 4 : 4

결과를 보시면 다음과 21 ~ 24 라인에서 함수의 인자가 모두 출력되는 것을 볼 수 있습니다.


int n = va_arg(ap, int); 은 계속해서 ap가 int에 따라 번지를 옮기면서, 값을 리턴하여 n에 넣어준다고 생각하면 됩니다.

va_start, va_end는 가변인자를 읽을 때의 시작과 끝을 뜻하는 것입니다.

이러한 가변인자의 관련한 함수를 사용하기 위해서는 cstdarg (또는 stdarg.h) 헤더 파일을 이용하면 됩니다.


 3. 가변 인자를 사용하는 방법. (2) - 서로 다른 데이터형을 출력할 때.

위의 예제를 보시면 하나의 데이터형(int) 만 출력하는 것을 볼 수 있습니다.

따라서 이번에는 다양한 데이터형을 출력하는 방법을 소개하겠습니다.


VariableEx4.cpp

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <cstdarg>
 
using namespace std;
 
void PrintNums(char* dataType, ...)
{
    va_list ap;
    int count = 0;
 
    va_start(ap, dataType);
 
    while (dataType[count] != '\0') {
    switch (dataType[count]) {
    case 'i' :
        cout << "args : " << va_arg(ap, int<< " ";
        break;
    case 'c' :
        cout << "args : " << (char)va_arg(ap, int<< " ";
        break;
    case 'd' :
        cout << "args : " << va_arg(ap, double<< " ";
        break;
    case 's' :
        cout << "args : " << va_arg(ap, char*<< " ";
        break;
    default :
        break;
    }
    count++;
    }
    va_end(ap);
    cout << endl;
}
 
int main(void)
{
    PrintNums("i"1);
    PrintNums("ic"1's');
    PrintNums("sic""Variable Ex"2'h');
    PrintNums("sdci""Variable Ex"2.128923'h'4);
    return 0;
}
cs


PrintNums("sdci", "Variable Ex", 2.128923, 'h', 4); 을 보시면 첫 번째 인자에서 각 자료형의 첫 이름을 보내는 것을 확인할 수 있습니다.

이후, 13 ~ 28 라인에서 문자 하나씩 비교하여, 자료형에 따라서 출력하는 것을 확인할 수 있습니다.


이러한 자료형의 첫 이름은 숫자는 'd'이고, 문자열은 's' 이고... 이런건 아닙니다. 사용자가 정의한 대로 사용하면 됩니다.

그런데, 이상한 점을 발견할 수 있는데요. 예제의 19번 라인을 보시면 문자를 보내지만 va_arg(ap, int)로 출력하는 것을 확인할 수 있습니다.


저는 리눅스(우분투)에서 개발하기 때문에 그렇습니다. visual studio에서는 이렇게 하지 않아도 된다고 합니다. 이러한 환경 별 차이점은 다음의 표로 정리하였습니다.


 환경 별 가변인자에서 사용하는 자료형 차이점.

 Visual Studio

 char,bool

 short

 float

 GCC

 int

 int

 double


혹시, Visual Studio에서 위의 예제를 확인하고자 하시는 분은 저의 git주소에서 VariableEx3.cpp 을 확인하시면 되겠습니다.

이상 포스팅을 마치겠습니다.


참고 자료 : 코딩도장

 soen.kr



제 글이 도움이 되셨거나 공감이 되시는 부분이 있으셨다면, 밑에 있는 공감 버튼 한 번씩 꾸욱 눌러주시면 감사하겠습니다.


공감 버튼은 저에게 큰 도움이 됩니다. 감사합니다.

댓글