在调用支持可变参数格式化的函数时的一个很掩蔽问题

        很多时候我们都希望一些函数能够支持可变参数,这样的函数或是用来进行字符串的格式化,或是调用函数时函数本身支持参数的格式化(这样在调用函数之前不用另外格式化,调用的函数本身就支持参数的格式化)。比如C语言中的printf函数,MFC中的CString的Format函数。它们内部均是使用va_start、va_list、va_end实现参数的解析的。下面给出duilib中的CStdString::Format 的函数实现,简单的查看一下是如何解析的。

int CStdString::Format( LPCTSTR pstrFormat, ... )
{
    CStdString sFormat = pstrFormat;
    // Do ordinary printf replacements
    // NOTE: Documented max-length of _vstprintf() is 1024
    TCHAR szBuffer[1025] = { 0 };
    va_list argList;
    va_start( argList, pstrFormat );

    // wvsprintf不支持浮点格式,所以换成_vstprintf
    int iRet = ::_vstprintf( szBuffer, sFormat, argList );
    va_end( argList );
    Assign( szBuffer );
    return iRet;
}

       上面简要的说明了使用va_start、va_list、va_end实现了对可变参数格式化的支持,那其中的陷阱在什么地方呢?在日常的代码开发过程中,为了定位问题我们需要添加相关的打印信息,或打印到控制台窗口上,或是将日志写入到文件中。我们就以C语言中打印输出函数printf为例吧。该函数支持直接将字符串打印出来,也支持对可变参数的格式化输出,问题就掩藏在直接打印字符串这个功能上。大家看下面的代码会不会出问题:

char achBuf[] = {"this is a test %d"}; // 包含格式化符%d
printf(achBuf);
       上面的代码可以测试一下是没问题,achBuf中包含了一个%d的格式化,但是调用achBuf时,并没有传待格式化的参数,运行可能会出问题,但实际上运行时是不会报错的。那我们将字符串中的%d改为%s后,再测试一下,结果程序发生崩溃了。那为什么%d没问题,而%s就出现了崩溃呢?可能是在参数列表中可以随便找个整数值,但字符串就不太好找了。 因为没有传待格式化的字符串参数,所以产生了崩溃。

       上述代码只是一些测试代码,是我们人为的在待输出的字符串中加入了格式化符,在实际代码运行的过程中,一般不会出现这样的字符串中包含有格式化的情况。那在哪些个别情况下会出现呢?比如在聊天对话框中,用户可以随意的输入,也可能聊天的双方也在讨论编程的问题,就有可能出现输入包括%s的字符串了。我们可能为了定位问题,直接将用户输入的内容打印出来,调用支持可变参数格式化的函数将字符串直接打印出来,此时就可能出现崩溃。因为出现问题的场景比较少,所以这个问题非常隐蔽,很难被发现。这个问题在我们实际的代码开发过程中发生过两次,一次是重构代码发现的,一次是排查崩溃代码发现的。

        这个问题对于几乎所有支持可变参数格式化的函数,都是存在的,不管是系统函数还是用户自定义函数。那这个问题该如何解决呢?可以采用规避的办法,不直接打印字符串,而是将字符串作为待格式化的参数传进去,即如下所示:

char achBuf[] = {"this is a test %s"}; // 包含格式化符%s
printf("%s", achBuf);
这样就没有问题了,可以测试一下。

        所以在直接打印用户输入字符串的场合,就要特别留意这个问题了。

你可能感兴趣的:(在调用支持可变参数格式化的函数时的一个很掩蔽问题)