sprintf_sswprintf_s 在 Release 和 Debug 不同模式表现不同

int sprintf_s( char /buffer, size_t sizeOfBuffer, const char /format [, argument] ... );
int swprintf_s( wchar_t /buffer, size_t sizeOfBuffer, const wchar_t /format [, argument]... );

msdn 对第二个参数的解释仅仅是"Maximum number of characters to store." 即,可存储的最大字符个数。按照常规(至少我)的理解是这样去理解这个参数的: 当输入的字符个数大于 sizeOfBuffer 时,会进行截断等限制, 用以保证不会让 buffer 溢出;当输入的个数小于或者等于 sizeOfBuffer 时,正常赋值即可,sprintf 和 sprintf_s 的区别仅在于加了一个 sizeOfBuffer 的判断。

msdn 的解释是:

The other main difference between sprintf_s and sprintf is that sprintf_s takes a length parameter specifying the size of the output buffer in characters. If the buffer is too small for the text being printed then the buffer is set to an empty string and the invalid parameter handler is invoked.

可是实际上,在 Debug 和 Release 下 sprintf_s 表现形式不同:

Debug 模式:

#include <iostream>
#include <cstdio>

int main()
{
    const int buf_size = 32;

    char buf1[buf_size];
    memset(buf1, 0, buf_size);
    char buf2[buf_size];
    memset(buf2, 1, buf_size);
    // breakpoint1
    sprintf_s(buf2, buf_size*2, "%s", "hello, world!");
    // breakpoint2
}

在 sprintf_s 上下插入一个端点,如上代码注释,breakpoint1 时内存块:

0x002EFC0C  01 01 01 01 01 01 01 01  ........    <-- buf2 [8 * 4 = 32]
0x002EFC14  01 01 01 01 01 01 01 01  ........
0x002EFC1C  01 01 01 01 01 01 01 01  ........
0x002EFC24  01 01 01 01 01 01 01 01  ........
0x002EFC2C  cc cc cc cc cc cc cc cc  ????????
0x002EFC34  00 00 00 00 00 00 00 00  ........    <-- buf1 [8 * 4  = 32]
0x002EFC3C  00 00 00 00 00 00 00 00  ........
0x002EFC44  00 00 00 00 00 00 00 00  ........
0x002EFC4C  00 00 00 00 00 00 00 00  ........

breakpoint2 时内存块:

<code>0x002EFC0C  68 65 6c 6c 6f 2c 20 77  hello, w    <-- buf2 [8 * 8 = 64]
0x002EFC14  6f 72 6c 64 21 00 fe fe  orld!.??
0x002EFC1C  fe fe fe fe fe fe fe fe  ????????
0x002EFC24  fe fe fe fe fe fe fe fe  ????????
0x002EFC2C  fe fe fe fe fe fe fe fe  ????????
0x002EFC34  fe fe fe fe fe fe fe fe  ????????    <-- buf1 [被覆盖了 8 * 3 = 24]
0x002EFC3C  fe fe fe fe fe fe fe fe  ????????
0x002EFC44  fe fe fe fe fe fe fe fe  ????????
0x002EFC4C  00 00 00 00 00 00 00 00  ........
</code>

可以看出由于 sizeOfBuffer 传入值大于 buf_size,导致了越界,覆盖了高地址的内存块(修改了 buf1 的值)。

Release 验证代码:

#include <iostream>
#include <cstdio>

int main()
{
    const int buf_size = 32;

    char buf1[buf_size];
    memset(buf1, 0, buf_size);
    std::cout << buf1 << std::endl;
    char buf2[buf_size];
    memset(buf2, 1, buf_size);

    int ret_size = sprintf_s(buf2, buf_size*4, "%s", "hello, world!");
    std::cout << ret_size << std::endl;
}

运行到最后的内存块:

0x0030F768  68 65 6c 6c 6f 2c 20 77  hello, w   <-- buf1 [8 * 4 = 32]
0x0030F770  6f 72 6c 64 21 00 01 01  orld!...
0x0030F778  01 01 01 01 01 01 01 01  ........
0x0030F780  01 01 01 01 01 01 01 01  ........
0x0030F788  00 00 00 00 00 00 00 00  ........   <-- buf2 [8 * 4 = 32]
0x0030F790  00 00 00 00 00 00 00 00  ........
0x0030F798  00 00 00 00 00 00 00 00  ........
0x0030F7A0  00 00 00 00 00 00 00 00  ........

尽管我刻意的修改了几次 sprintf_s 的 sizeOfBuffer 传入值,但是它始终没有越界。

总结:

我进行了源码分析,结果追踪到 _vsnprintf_s_l 这个方法以后,就找不到源码了,所以最终也没有看到微软对于 sprintf_s 的实现方式 (网上也没有搜到具体的源码实现)。我感觉造成这种不同有两种可能,一种是针对 DEBUG 和 RELEASE 分别对应两个实现版本;第二种是在 Release 模式下,编译器对代码安全检查和优化,而 Debug 模式下故意将问题暴漏出来。


sprintf_s, swprintf_s MSDN的解释

int sprintf_s(
   char *buffer,
   size_t sizeOfBuffer,
   const char *format [,
      argument] ...
);
int swprintf_s(
   wchar_t *buffer,
   size_t sizeOfBuffer,
   const wchar_t *format [,
      argument]...
);

Parameters

  1. buffer -> Storage location for output
  2. sizeOfBuffer -> Maximum number of characters to store.
  3. format -> Format-control string
  4. argument -> Optional arguments

Return Value

The number of characters written, or –1 if an error occurred. If buffer or format is a null pointer, sprintf_s and swprintf_s return -1 and set errno to EINVAL. sprintf_s returns the number of bytes stored in buffer, not counting the terminating null character. swprintf_s returns the number of wide characters stored in buffer, not counting the terminating null wide character.

Remarks

The sprintf_s function formats and stores a series of characters and values in buffer. Each argument (if any) is converted and output according to the corresponding format specification in format. The format consists of ordinary characters and has the same form and function as the format argument for printf. A null character is appended after the last character written. If copying occurs between strings that overlap, the behavior is undefined. One main difference between sprintf_s and sprintf is that sprintf_s checks the format string for valid formatting characters, whereas sprintf only checks if the format string or buffer are NULL pointers. If either check fails, the invalid parameter handler is invoked, as described in Parameter Validation. If execution is allowed to continue, the function returns -1 and sets errno to EINVAL. The other main difference between sprintf_s and sprintf is that sprintf_s takes a length parameter specifying the size of the output buffer in characters. If the buffer is too small for the text being printed then the buffer is set to an empty string and the invalid parameter handler is invoked. Unlike snprintf, sprintf_s guarantees that the buffer will be null-terminated (unless the buffer size is zero). swprintf_s is a wide-character version of sprintf_s; the pointer arguments to swprintf_s are wide-character strings. Detection of encoding errors in swprintf_s may differ from that in sprintf_s. The versions of these functions with the _l suffix are identical except that they use the locale parameter passed in instead of the current thread locale.

PS: 如果有不同的理解,或者找到了源码,欢迎追加。

First created: 2013-03-24 08:33:08
Last updated: 2022-12-11 Sun 12:49
Power by Emacs 27.1 (Org mode 9.4.4)