本文记录一个由错误使用StringCchVPrintf等格式化函数所导致的崩溃问题。

一. 问题描述

我们常用的格式化字符串函数有:

1
2
3
4
5
6
HRESULT StringCchVPrintf(
_Out_ LPTSTR pszDest,
_In_ size_t cchDest,
_In_ LPCTSTR pszFormat,
_In_ va_list argList
);
1
int printf ( const char * format, ... );

对于如下的调用:

1
2
char buf[100] = {0};
StringCchVPrintf(buf, 100, "select * from member where name like '%sjj%';");

此时buf中存储的并不是期望的select * from member where name like '%sjj%';字符串。不出意外,%sjj%处的%s会变成乱码。

因为函数将%sjj%中的%s当做了字符串格式化串了,而我们又没有给最后一个参数(即可变参数)传值,根据可变参数的原理,默认会根据format参数的地址来取一个地址让%s进行输出。

具体计算方式参考va_start宏定义:

1
2
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

这个地址的内容是未知的,所以就可能出现乱码或崩溃。

二. 解决方案

要解决这个问题,我们只需要做到一点,在调用StringCchVPrintf、vsprintf、vswprintf、_vstprintf、printf等函数时,一定不要将固定字符串传入到pszFormat参数,如:

1
StringCchVPrintf(szBuf, 512, "我想输出单纯的%s,我是错误的格式示范");  // 错误的

这个时候,单纯的%s中的%s已经不在单纯。

正确的做法是:

1
StringCchVPrintf(szBuf, 512, "%s", "我想输出单纯的%s,我是正确的格式示范"); 

同理,下面的调用方式也是错误的、危险的:

1
2
std::string strInfo = GetInfo();
printf(strInfo.c_str());

文章图片带有“CSDN”水印的说明:
由于该文章和图片最初发表在我的CSDN 博客中,因此图片被 CSDN 自动添加了水印。