Microsoft MVP성태의 닷넷 이야기
오류 유형: 230. C++ 가변 인자 사용시 va_start 파라미터 전달 방법 [링크 복사], [링크+제목 복사],
조회: 27070
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

C++ 가변 인자 사용시 va_start 파라미터 전달 방법

아래의 코드를 Visual Studio 디버그 모드로 실행하면,

#include "stdafx.h"

#include <stdarg.h>
#include <string>

using namespace std;

void OutputText2(wstring fmt, ...);

int _tmain(int argc, _TCHAR* argv[])
{
    OutputText2(L"test %s\n", L"test");
    return 0;
}

void OutputText2(wstring fmt, ...)
{
    const wchar_t *pFmt = fmt.c_str();
    va_list args;
    va_start(args, pFmt);

    vwprintf(pFmt, args);

    va_end(args);
}

output.c 소스 코드가 열리면서 비정상 종료를 알립니다.

varargs_vc_1.png

Unhandled exception at 0x0F85AFBB (msvcr120d.dll) in ConsoleApplication1.exe: 0xC0000005: Access violation reading location 0xCCCCCCCC.


이유가 뭘까요? ^^ 반면에 OutputText 메서드의 첫 번째 인자를 "wchar_t *"로 바꿔주면 정상적으로 실행됩니다.

void OutputText(wchar_t *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    vwprintf(fmt, args);

    va_end(args);
}

도대체 이유가 뭘까요? ^^ 원인은 MSDN 문서를 찾아보니 나옵니다.

va_arg, va_copy, va_end, va_start
; https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/va-arg-va-copy-va-end-va-start

va_start sets arg_ptr to the first optional argument in the list of arguments that's passed to the function. The argument arg_ptr must have the va_list type. The argument prev_param is the name of the required parameter that immediately precedes the first optional argument in the argument list.


즉 va_start의 두 번째 인자로 전달되는 값은, 가변 인자 중 첫 번째 바로 이전에 오는 인자를 전달해야 하는 것입니다. 따라서 OutputText2 함수는 다음과 같이 바뀌어야 합니다.

void OutputText2(wstring fmt, ...)
{
    const wchar_t *pFmt = fmt.c_str();
    va_list args;
    va_start(args, fmt);

    vwprintf(pFmt, args);

    va_end(args);
}

즉, 가변 인자가 전달되는 "스택" 기준의 앞에 있는 인자를 전달해야 하는 것입니다. 그래서 다음과 같은 설명도 나옵니다.

If prev_param is declared with the register storage class, the macro's behavior is undefined.


만약, 앞의 인자가 레지스터를 통해 전달되는 경우라면 va_start는 va_list를 잘못된 값으로 초기화할 것이고 그 이후의 동작은 예측할 수 없는 상태로 빠지는 것입니다. (대개의 경우, 비정상 종료)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 7/17/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2015-12-20 09시25분
[흠흠] "따라서 OutputText2 함수는 다음과 같이 바뀌어야 합니다." 이 뒤의 수정된 코드가 잘못됐지 싶은데요.
앞의 잘못된 코드와 같은 코드입니다.

그리고 위의 코드에서 문제의 원인은 prev_param 으로써 참조될 wstring fmt 매개변수가 레지스터 변수라서가 아니라 크기가 큰 개체이기 때문입니다.
va_start 는 prev_param 으로써 전달된 매개변수가 ia32 에선 32bit, x64 에선 64bit 라고 간주하여 그 뒷주소에서 가변인자를 읽기 시작하는데, wstring fmt 처럼 개체를 통째로 스택에 넘길 경우 fmt 개체가 이 크기를 초과하므로 가변 매개변수 시작주소 계산에 문제가 생깁니다.
그래서 prev_param 은 반드시 포인터 내지 long 형으로만 넘겨야 합니다.
물론 가변인자들 역시 ia32 에선 32bit, x64 에선 64bit 단위로 삽입해야 하구요.
[guest]
2015-12-21 01시23분
제가 쓴 글을 약간 혼동하신 것 같은데요. 위의 글에서 첫 번째 소스코드의 문제 원인은 wstring fmt 변수가 "레지스터 변수라서"가 아닙니다. 마지막의 레지스터 관련해서는 그런 경우에도 잘못될 수 있다는 것을 말한 것뿐입니다.

그리고, 첫 번째 인자(prev_param)로 32비트/64비트 제약이 있다는 것은 어디에 근거를 두신 것인지요? 첫 번째 인자는 스택 크기만 제대로 반환할 수 있다면 어떠한 구조체(클래스)가 와도 무방합니다. 결국 va_start 내에서는 sizeof(...)를 통해 해당 크기만큼 포인터를 이동하는 것에 불과하기 때문에 포인터에 준하는 크기를 가질 필요가 없는 것입니다.

혹시, 첫 번째 인자의 타입 크기가 32/64가 아니라서 문제가 발생하는 경우를 재현해 주실 수 있나요?

그리고, 가변인자들 역시 (단지, 인자들이 전달될 때 데이터의 크기가 CPU에 따른 정렬은 되겠지만) 32비트/64비트 단위로 삽입하는 것은 강제 사항이 아닙니다. 만약 C 언어에 3바이트 정수 타입(예를 들어, int3)이 있었다면, printf를 만든 프로그래머는 형식 매개변수에 (%d가 아닌, 3바이트 정수를 전달받을 수 있는) %int3d라는 것을 추가했을 것입니다. (사실, 임의로 소스코드를 고친다면 현재도 3바이트 구조체를 만들어 전달하는 것이 가능합니다.)

참고로 가변인자는 va_list, va_start, va_arg로 인해 약간 스펙처럼 여겨질 수 있는데, 이것은 helper용 macro에 불과할 뿐 구현하는 측에서 전달된 스택의 매개변수에 대해 포인터 이동을 수작업으로 얼마든지 구현하는 것이 가능합니다. 단지 귀찮을 뿐!
정성태

... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1302정성태6/25/201229315개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225728오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231703.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232805제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234365VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201230971VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227657.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225037.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248494.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229740.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223723.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230236VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235062.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239207.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226425.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229233.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238148.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233248.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225671오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233277.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226072Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233144.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226135오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224892.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226115오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1277정성태5/8/201231266오류 유형: 152. cmd.exe - The system cannot write to the specified device. [2]
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...