어떤 것을 쓰면 좋을까요? ^^ wvnsprintf, _vsnwprintf_s, StringCbVPrintfW
물론 상황에 따라 다르겠지요. ^^
일단 wvnsprintf를 사용하는 예제 먼저 볼까요?
void OutputText(const wchar_t *format, ...)
{
wchar_t buffer[10];
va_list vaList;
va_start(vaList, format);
int retval = wvnsprintf(buffer, 10, format, vaList); // retval == -1
va_end(vaList);
printf("%S\n", buffer);
}
int _tmain(int argc, _TCHAR* argv[])
{
OutputText1(L"%d12345678901234567890", 0); // 출력결과: 012345678
return 0;
}
위의 경우 OutputText 함수 내에 10개의 버퍼(wchar_t이므로 20바이트)를 마련했지만 인자로 들어온 값들이 이를 넘어서는 경우 반환값이 -1로 나옵니다.
wvnsprintf function
; https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-wvnsprintfa
하지만 문서에는 이에 대한 사용을 하지 말라고 나옵니다.
Do not use this function. See Remarks for alternative functions.
Security Warning: Using this function incorrectly can compromise the security of your application. The copied string is not guaranteed to be null-terminated. Consider using one of the following alternatives. StringCbPrintf, StringCbPrintfEx, StringCbVPrintf, StringCbVPrintfEx, StringCchPrintf, StringCchPrintfEx, StringCchVPrintf, or StringCchVPrintfEx. You should review Security Considerations: Microsoft Windows Shell before continuing.
근데 테스트해 보면 버퍼가 적어도 마지막을 '\0' 널 문자로 채워주었습니다. 아마도 예전 버전에서 그랬던 것인지, 아니면 특정 상황에서 그럴 수 있는지는 알 수 없으나 어쨌든 경고 문구가 있으니 달갑지는 않습니다. ^^
여기서 결론을 내면 좀 아쉽지만 결국 답은 StringCbVPrintfW를 사용하는 것이 좋습니다.
#include <Strsafe.h>
void OutputText3(const wchar_t *format, ...)
{
wchar_t buffer[10];
va_list vaList;
va_start(vaList, format);
int retval = StringCbVPrintfW(buffer, sizeof(wchar_t) * 10, format, vaList); // retval == -2147024774
va_end(vaList);
printf("%S", buffer);
}
OutputText3(L"%d12345678901234567890", 0);
StringCbVPrintfW가 wvnsprintf와 다른 점이 있다면 버퍼가 모자른 경우 반환값이 -1이 아니라 0x8007007A(-2147024774: The data area passed to a system call is too small.)로 나오는 점입니다.
그리고 버퍼 카운트가 wchar_t라고 해서 그 배열의 수를 그대로 전달하면 안되고 순수하게 byte 수를 전달해야 한다는 점입니다. 따라서 wvnsprintf는 버퍼 수를 10으로 줘도 되었지만 StringCbVPrintfW는 10 * sizeof(wchar_t)로 해야 합니다.
이제, 마지막으로 _vsnwprintf_s가 있는데... 이건 사용시 주의를 요합니다. wvnsprintf와는 달리 string safe 버전이기 때문에 대상 버퍼에 대한 관리가 특별합니다.
wchar_t buffer[10];
va_list vaList;
va_start(vaList, format);
int retval = _vsnwprintf_s(buffer, 10, format, vaList);
va_end(vaList);
단순히 사용하게 되면 버퍼가 모자르는 상황에 대해 디버그 빌드일 때는 예외 발생, 릴리스 빌드일 때는 프로그램 비정상 종료로 이어집니다.
Debug Assertioin failed!
Program:
...
File: f:\dd\vctools\crt\crtw32\stdio\vswprint.c
Line: 358
Expression: ("Buffer too small", 0)
For information on how your program can cause an assertion failure, see the Visual C++ documentation on asserts.
(Press Retry to debug the applicaiton)
그래서 아무 생각 없이 안전한 환경에서 사용하다가 릴리스 후 예측할 수 없는 클라이언트 환경에서 뜻하지 않은 오동작을 만날 수 있습니다.
다행히 _vsnwprintf_s는 wvnsprintf처럼 동작하는 방식도 지원합니다. 이를 위해 버퍼 수를 _TRUNCATE 전처리 상수로 지정하면 됩니다. (_TRUNCATE는 crtdefs.h파일에 -1로 정의되어 있습니다.)
wchar_t buffer[10];
va_list vaList;
va_start(vaList, format);
int retval = _vsnwprintf_s(buffer, _TRUNCATE, format, vaList);
va_end(vaList);
위의 코드는 wvnsprintf와 동작 방식이 같습니다.
정리해 보면 _vsnwprintf_s와 StringCbVPrintfW 함수를 사용하는 것이 권장됩니다.
대신 _vsnwprintf_s의 경우 예외 발생으로 인해 주의를 요하는데 디버그 시에는 확실히 문제를 인지할 수 있도록 버퍼를 지정하는 것이 좋고, 릴리스 시에는 _TRUNCATE로 처리하는 것이 좋습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]