가변 인자 목록을 사용하는 함수

J·2025년 5월 13일

테스트

목록 보기
7/12

Log를 저장하는 클래스를 생성하기 전에 가변 인자 목록을 사용함수를 이용해야 할 것 같은데, 기본적인 동작의 이해를 위해 작성.

※매우 주관적임.

// ParserTest.cpp : 이 파일에는 'main' 함수가 포함됩니다. 거기서 프로그램 실행이 시작되고 종료됩니다.
//
#include <Windows.h>
#include <iostream>

#include <crtdbg.h>

// main에서 호출하는 실행 함수
void VariadicParamTest();

// main -> VariadicParamTest()에서 호출
void VariadicFunc(int first, int n,...);

// 가변 인자 출력을 위한 함수
// main -> VariadicParamTest() -> PrintVariadicArgsInfo()
void PrintVariadicArgsInfo(const WCHAR* msg, ...);

int main()
{
    _wsetlocale(LC_ALL, L"korean");

    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    VariadicParamTest();
      
    return 0;
}

void VariadicParamTest()
{
    WCHAR first = 0x1111;
    short second = 0x2222;
    int third = 0x33333333;
    __int64 fourth = 0x1234'5678'90ab'cdef;
    
    VariadicFunc(100, 1200, first, second, third, fourth, second, first);
}

// 가변 인자 사용 함수.
void VariadicFunc(int first, int n,...)
{
    // va_start, va_list 매크로로 들어온 매개변수에 대한 접근을 한다.
    
    // 접근하기 위해서는 va_list를 사용해야한다.
    va_list argPtr = nullptr;
    
    // ★가변 매개변수를 사용하기 전에 필수 매개변수를 이용해서 포지션을 잡아줘야한다.
    // +-----------------------------------------------------------------+
    // |                                                                 
    // |  (param1, param2, 마지막 param3, ... (가변)) 일 때                
    // | .param3을 등록해줘야한다.                                         
    // |                                                                 
    // |  가변 인자를 사용하기 위해선 필수 매개변수가 존재해야한다.              
    // |  최소 : Func(type param, ...)                                                                                                  
    // |                                                                 
    // |                                                                 
    // |  [순서]                                                         
    // |                                                                 
    // |  1.★                                                           
    // |  va_Start(va_list, param)  --> va_list는 param으로 전달된       
    // |  매개변수의 다음 주소 (즉 &가변 매개변수[0]) 를 가리킴          
    // |                                                                 
    // |  2.★                                                           
    // |  Type userVar = va_arg(va_list, Type)                           
    // |  va_list가 가지고 있는 주소의 값을 Type으로 캐스팅 후 대입      
    // |  그 후에 포인터 크기만큼을 옮겨주지만                           
    // |  32bit에서 64비트 데이터의 경우 2개의 포인터크기만큼을 올려준다.
    // |                                                                 
    // |                                                                 
    // |  3.★                                                           
    // |  va_end(va_list)                                                
    // |  va_list의 값을 nullptr로 밀어준다.                             
    // |                                                                 
    // |                                                                 
    // | 어떤 매개변수든 넣을 수 있지만, 각 타입에 따른 처리는           
    // | 사용자가 처리를 할 수 있도록 만들어야한다.                      
    // |                                                                 
    // |-----------------------------------------------------------------|
    // | vwprintf(const wchar* msgFormat, ...)                           
    // | 위의 va_list, va_start, va_end를 활용하는 것은 동일             
    // |                                                                 
    // | 활용은 포맷과 가변 인자를 넣어주면 OK                           
    // | vwprintf(msgFormat, va_list)                                    
    // |                                                                 
    // | va_list를 순회하면서 Format에 맞게 출력                         
    // | Format의 매개변수 수 < 가변 매개 변수 수 -> 전부 출력 안됨      
    // | Format의 매개변수 수 > 가변 매개 변수 수 -> 쓰레기 데이터 출력                                                                |
    // |                                                                 
    // +-----------------------------------------------------------------+



    //va_start(argPtr, first);
    va_start(argPtr, n);

    // va_arg(va_list, type)
    // va_start을 통해 
    int i = 0;

    while (true)
    {
        void* prevPtr = argPtr;
        

        // WCHAR -> SHORT -> INT -> _INT64 -> SHORT -> WCHAR 형태로 출력
        switch (i)
        {
        case 0:
        case 5:
        {
            WCHAR value = va_arg(argPtr, WCHAR);
            PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", value, prevPtr, argPtr);
            //PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", va_arg(argPtr, WCHAR), prevPtr, argPtr);
        }
            break;
        case 1:
        case 4:
        {
            short value = va_arg(argPtr, short);
            PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", value, prevPtr, argPtr);
            //PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", va_arg(argPtr, short), prevPtr, argPtr);
        }
        break;
        case 2:
        {
            int value = va_arg(argPtr, int);
            PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", value, prevPtr, argPtr);
            //PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", va_arg(argPtr, int), prevPtr, argPtr);
        }   
        break;
        case 3:
        {  
            LONGLONG value = va_arg(argPtr, __int64);

            // 64바이트 출력을 위해선 %llx를 사용하자!
            PrintVariadicArgsInfo(L"Value : 0x%0llx, prevPtr : [0x%p], ptr : [0x%p]\n", value, prevPtr, argPtr);
            //PrintVariadicArgsInfo(L"Value : 0x%0llx, prevPtr : [0x%p], ptr : [0x%p]\n", va_arg(argPtr, __int64), prevPtr, argPtr);
        }   
        break;
        default:           
            // 사용자가 따로 매개변수를 넘어서서 접근했을 때 오류가 발생하는지의 확인을 위해 추가함
            PrintVariadicArgsInfo(L"Value : 0x%0x, prevPtr : [0x%p], ptr : [0x%p]\n", va_arg(argPtr, WCHAR), prevPtr, argPtr);
            break;
        }

        
        if (prevPtr == argPtr)
            break;

        i++;
    }
    va_end(argPtr);
}

void PrintVariadicArgsInfo(const WCHAR* msg, ...)
{
    // 생성하고
    va_list vaList;
    
    // 열어주고
    va_start(vaList, msg);

    int messageCount = vwprintf_s(msg, vaList);

    if (messageCount < 0)
    {
        wprintf(L"MessageCount < 0 이면 Error입니다.");
    }
    // 닫아주고
    va_end(vaList);
}

// 32bit 기준 8byte (__int64) 도 매개변수로 저장할 때는 4*2칸의 공간을 그대로 할당해버림

사용/이해 정리

  • 가변 인자 사용을 위해선 (필수 매개변수 + ...) 의 형태로 사용

  • va_list / va_start / va_end

  • va_list는 가변 인자에 접근 위한 포인터 (char*로 정의되어 있음.)

  • ★ ( 32bit 환경에서 __int64값이 필수 매개변수 주소라면 포인터 크기(4) * 2 만큼 움직임) ☆

  • va_start(va_list, 필수 매개변수)
    - va_list = (필수 매개변수 주소) + 포인터 크기

  • Type 변수 = va_arg(va_list, Type)
    - va_list를 (Type)한 값을 변수에 대입 한 후 va_list += 포인터 크기
    ==> 변수 =
    ((Type*)va_list); va_list += 포인터 크기

  • va_end(va_list)

    • va_list = nullptr;
  • vwprintf(const wchar* format, ...)

    • 가변 인자들 순회하며 출력하는 형태로 사용할 때 좋아보임.

다만 가변 인자를 활용할 때 넣는 매개변수들에 대한 처리는 사용자가 해줘야 한다.

- va_list를 순회하면서 Format에 맞게 출력                         
- Format의 매개변수 수 < 가변 매개 변수 수 -> 전부 출력 안됨      
- Format의 매개변수 수 > 가변 매개 변수 수 -> 쓰레기 데이터 출력 

결국 사용할 때 프로그래머가 알아서 잘! 사용해야 된다는 게 내가 느낀 것이다.

  1. va_arg(__int64) 읽기 전

  2. va_arg(__int64) 읽은 후

  3. va_arg(__int64) 전 / 후 메모리

profile
낙서장

0개의 댓글