본문 바로가기
Language/C언어

C017_함수, 가변인자 사용하기

by OdOp 관리자 2023. 9. 19.
SMALL

가변인자는 함수에 몇 개의 인자가 필요한지 모를 때에 사용합니다. 

인자가 3개 들어올지, 4개 들어올지 모를 때에 가변인자를 사용하면 몇 개가 들어와도 상관이 없게 됩니다. 

 

#include <stdio.h>

void printNumber(int args, ...)
{
    printf("%d ", args);
}

int main()
{
    printNumber(1, 10);
    printNumber(2, 10, 20);
    printNumber(3, 10, 20, 30);
    printNumber(4, 10, 20, 30, 40);
    printf("\n");
    return 0;
}

가변인자를 사용하려면 위와 같이

'함수이름(자료형 변수이름, ...)'

이렇게 사용하면 됩니다. 위의 예제에서는 'printNumber(int args, ...)'으로 사용했습니다. 

하지만 위의 출력 결과물은 '1 2 3 4 '입니다.

왜 그럴까요??

가변인자는 포인터로 불러오게 됩니다. 각각의 주소를 알아내서 사용하는 것이지요. 위의 예제에서는 그 주소를 불러오지를 않고 있습니다. 

(포인터를 잘 모르시다면 아래의 포스팅을 보시면 이해가 되실 것이라고 생각 듭니다.)

https://hig0617.tistory.com/15

 

 

그면 주소를 어떻게 설정을 할까요???

우선 시작주소를 가리킬 변수를 하나 만들어야 합니다. 

다음으로 그 시작주소가 정확히 어디서 시작하는지 알려주어야 합니다. 

이제 주소를 이용하여 원하는 크기만큼 꺼내오면 됩니다. 

마지막으로 처리가 모두 끝났을 때에 malloc을 free 해주듯이 위에서 만들었던 변수를 NULL로 초기화해 줍니다. 

조금 어려우신가요??

예제를 보시면 이해하실 수 있으시길 바랍니다. 

 

가변인자를 사용하여 숫자 개수만큼 숫자 출력하기

#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++)
    {
        printf("%d ",va_arg(ap, int));  //int 크기만큼 가변 인자 목록 포인터에서 값을 가져옴, ap를 int 크기만큼 순방향으로 이동
    }
    va_end(ap);  //가변 인자 목록 포인터를 NULL로 초기화 
    printf("\n");
}

int main()
{
    printNumbers(1, 10);
    printNumbers(2, 10, 20);
    printNumbers(3, 10, 20, 30);
    printNumbers(4, 10, 20, 30, 40);
    return 0;
}

printNumbers라는 함수는 args로 숫자가 몇 개가 입력될지 개수를 입력받고, 하나하나 출력해 주는 함수입니다. 

우선 시작주소를 가리킬 변수를 하나 만들어야 합니다.

va_list ap;

각 가변 인자의 시작 주소를 가리킬 포인터를 설정해 줍니다. 

 

다음으로 그 시작주소가 정확히 어디서 시작하는지 알려주어야 합니다. 

저희가 printNumbers를 보시면 첫 번째로 입력하는 인자가 가변인자가 몇 개 들어갈지를 알려주는 인자입니다. 그래서 시작주소는 이 첫 번째 인자의 크기만큼 건너뛰고 시작해야 합니다. 

va_start(ap, args);

args의 크기만큼 건너뛰고 메모리 주소를 시작한다. 이런 뜻입니다. 

'printNumbers(4, 10, 20, 30, 40)'여기서는 args가 4가 되겠지요.

 

이제 주소를 이용하여 원하는 크기만큼 꺼내오면 됩니다. 

일단 위의 예제에서는 모두 int형이니 int형만큼 순차적으로 꺼내오면 되겠죠.

va_arg(ap, int);

int형만큼 값을 가져오고 그 값을 사용했으니 int크기만큼 이동해 주어라 그런 뜻입니다. 

참고로, char, short의 경우에는 int로 대신 사용해야 하고, float의 경우에는 double로 대신 사용한 후 형 변환을 해주어야 합니다. 이 경우는 아래에서 조금 더 자세히 설명해 드리겠습니다. 

 

마지막으로 처리가 모두 끝났을 때에 malloc을 free 해주듯이 위에서 만들었던 변수를 NULL로 초기화해 줍니다. 

다 사용이 완료되었으면 포인터 주소를 NULL로 설정해 주어야 합니다. 

va_end(ap);

 

저희는 가변인자를 사용하기 위해 va_list, va_start, va_arg, va_end를 사용했습니다. 이를 사용하기 위해서는 헤더파일이 필요합니다. 위의 4개의 함수가 선언된 헤더파일은 stdarg.h라는 헤더파일이니 가변인자를 사용하실 때에 선언해 주시길 바랍니다. 

 

다양한 자료형 출력하기

#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')
        //"\0"로하면 오류 발생 '\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 ", (char)va_arg(ap, int));  //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("sdci", "Hello, world!", 1.234567, 'a', 10);  //문자열, 실수, 문자, 정수
    return 0;
}

printValues라는 함수는 어떤 자료형이 입력될지 types로 입력을 받고 하나하나 자료형대로 출력하는 함수입니다. 

위에서 달라진 부분만 설명드릴 예정이라 윗부분이 이해 안 되신 분은 다시 읽어주시길 바랍니다. 

switch ~ case문에 대한 설명은 아래의 포스팅에 설명이 되어 있습니다. 모르시는 분들은 참고 부탁드립니다. 

https://hig0617.tistory.com/10

 

모든 문자열은 null로 끝납니다. c언어는 문자열의 끝을 null로 알아냅니다. 

예를 들어 저희가 c언어를 처음 시작할 때에 사용하는 코드

printf("%s\n", "Hello, world!");

Hello, world!의 문자열의 끝을 어떻게 알아낼까요??? 마지막에 NULL이 자동적으로 들어가 있게 되어 알게 됩니다. 

위에서도 마찬가지로 types의 문자열은 모두 NULL로 끝나게 됩니다. 'sdci'로 예를 들자면, 

sdci문자열 설명

위와 같이 마지막에는 무조건 NULL(\0)이 들어갑니다. 

저희는 types를 index번호로 하나하나 읽으면서 NULL이 나오기 직전까지 읽습니다. 

i면 int형,

d면 double형,

c면 char형,

s면 char*형 즉, 문자열로 설정을 하겠습니다. 

switch ~ case문을 사용하여 하나하나 분리하여 읽고 출력하게 됩니다. 근데 여기서 위에서 설명드렸듯이 주의해주셔야 할 부분이 있습니다. 

위에서 char형을 출력할 때에 'va_arg(ap, char)'이 아니라 'va_arg(ap, int)'를 사용해야 한다고 설명을 드렸습니다. 

'va_arg(ap, char)'을 사용하면 경고 메시지가 나오기 때문입니다. 

warning: 'char' is promoted to 'int' when passed through '...'

해석해 보면 '...을 통과할 때면 char이 int로 승격된다. '라는 뜻입니다.  즉, 가변인자를 사용할 때면 char이 아닌 int로 사용해야 한다라는 뜻입니다. 

이렇게 주의해야 할 부분이 몇 가지 있지만 조금만 잘 피하신다면 가변인자를 누구보다 쉽게 사용하실 수 있으실 거라고 생각됩니다. 

긴 포스팅 읽어 주셔서 감사드립니다. 

다음에 더 좋은 포스팅으로 찾아뵙겠습니다. 

LIST

'Language > C언어' 카테고리의 다른 글

C019_재귀 함수 사용하기  (0) 2023.09.21
C018_비트 연산  (0) 2023.09.20
C016_함수, 매개변수 사용  (0) 2023.09.18
C015_함수에 대해  (3) 2023.09.17
C014_구조체 정렬하기  (0) 2023.09.16