Microsoft MVP성태의 닷넷 이야기
오류 유형: 230. C++ 가변 인자 사용시 va_start 파라미터 전달 방법 [링크 복사], [링크+제목 복사],
조회: 28515
글쓴 사람
정성태 (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에 불과할 뿐 구현하는 측에서 전달된 스택의 매개변수에 대해 포인터 이동을 수작업으로 얼마든지 구현하는 것이 가능합니다. 단지 귀찮을 뿐!
정성태

... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...
NoWriterDateCnt.TitleFile(s)
1739정성태8/24/201427737.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201423440.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201420921VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201426911VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201419394.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201421147오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201427493.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201435810Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201428283개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201423468개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
1729정성태8/11/201419462오류 유형: 236. SqlConnection - The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.
1728정성태8/8/201431697.NET Framework: 453. C# - 오피스 파워포인트(Powerpoint) 파일을 WinForm에서 보는 방법파일 다운로드1
1727정성태8/6/201421900오류 유형: 235. SignalR 오류 메시지 - Counter 'Messages Bus Messages Published Total' does not exist in the specified Category. [2]
1726정성태8/6/201420704오류 유형: 234. IIS Express에서 COM+ 사용 시 SecurityException - "Requested registry access is not allowed" 발생
1725정성태8/6/201422652오류 유형: 233. Visual Studio 2013 Update3 적용 후 Microsoft.VisualStudio.Web.PageInspector.Runtime 모듈에 대한 FileNotFoundException 예외 발생
1724정성태8/5/201427450.NET Framework: 452. .NET System.Threading.Thread 개체에서 Native Thread Id를 구하는 방법 - 두 번째 이야기 [1]파일 다운로드1
1723정성태7/29/201459841개발 환경 구성: 233. DirectX 9 예제 프로젝트 빌드하는 방법 [3]파일 다운로드1
1722정성태7/25/201422181오류 유형: 232. IIS 500 Internal Server Error - NTFS 암호화된 폴더에 웹 애플리케이션이 위치한 경우
1721정성태7/24/201425480.NET Framework: 451. 함수형 프로그래밍 개념 - 리스트 해석(List Comprehension)과 순수 함수 [2]
1720정성태7/23/201423454개발 환경 구성: 232. C:\WINDOWS\system32\LogFiles\HTTPERR 폴더에 로그 파일을 남기지 않는 설정
1719정성태7/22/201427337Math: 13. 동전을 여러 더미로 나누는 경우의 수 세기(Partition Number) - 두 번째 이야기파일 다운로드1
1718정성태7/19/201436778Math: 12. HTML에서 수학 관련 기호/수식을 표현하기 위한 방법 - MathJax.js [4]
1716정성태7/17/201436483개발 환경 구성: 231. PC 용 무료 안드로이드 에뮬레이터 - genymotion
1715정성태7/13/201431573기타: 47. 운영체제 종료 후에도 USB 외장 하드의 전원이 꺼지지 않는 경우 [3]
1714정성태7/11/201421571VS.NET IDE: 92. Visual Studio 2013을 지원하는 IL Support 확장 도구
1713정성태7/11/201445343Windows: 98. 윈도우 시스템 디스크 용량 확보를 위한 "Package Cache" 폴더 이동 [1]
... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...