字符串函数sprintf / sprintf_s的陷阱

其实,用C/C++做开发的童鞋,对sprintf不会陌生,对该函数的一些问题,一直想好好总结下:

如果第2个format(格式)参数中用%指定的后续参数个数与实际参数必须一致,否则可能会出各种问题。所谓一致是严格一致,看几个例子:

1. 如果你用的%d,对应的实参却用double,将得到错误的结果,而且更为严重的是,如果该参数后还有别的参数,将影响后面的参数解析:

char buf[1024];

double dV = 123.0;

int iLen = sprintf( buf, "Test:  %d, not one %d\n", dV, 1);

std::cout << buf;

并不会输出期望的结果:Test:  123, not one 1

我在VS2013下得到的输出是: Test:  0, not one 1079951360

如果我们知道函数调用和参数传递的机理,就会明白,sprintf 期望format参数后接2个int型参数(都是4字节的),但却给了个double参数和一个常数,我们知道double是8字节的,所以double就被分为了2个int,而后面的常数1压根就没用上,所以结果不可能正确。

结果要正确,必须将double参数强转成int:

int iLen = sprintf( buf, "Test:  %d, not one %d\n", (int)dV, 1);

2. format中指定的参数数目与实际的数目不一致时,也得不到正确的结果:

int iLen = sprintf( buf, "Test:  %d, not one %d\n", 1);    // 实参少1个

VS2013下得到的输出:Test:  1, not one -84618255

其实后面显示的数是随机的,就是函数调用栈实参1后面的一个4字节数,这个例子不会导致宕机,因为第2个实参取的是栈上的随机数(未初始化数)。


3. 如果format参数中有%s,则对应的实参一定得给对了,否则极其可能宕机:

char buf[1024];

double dV = 123.0;

int iLen = sprintf( buf, "Test:  %s, not one %d\n", dV, 1);

std::cout << buf;

在Debug模式下,将断点设置在最后一行,将会发现调用栈完全不对了,因为sprintf将dV的值当字符串地址看待,所以宕掉

char buf[1024];

std::string str = "hello";

int iLen = sprintf( buf, "Test:  %s, not one %d\n", str, 1);

std::cout << buf;

这个也不会输出正常结果, 会把str对象(copy的对象)的空间当作字符串地址

VS2013下得到的输出:  Test:  (null), not one -858993460 (还算幸运,没宕机)

sprintf不会帮你做字符串转换,你的主动告诉它。


最后,第1个缓冲区参数,需要有足够的空间,否则很容易越界导致各种问题。

sprintf_s在缓冲区后加了个缓冲区大小参数,VS的实现会将缓冲区按缓冲区大小参数memset,所以,如果缓冲区大小参数不正确,又将可能导致新的问题--越界


展开阅读全文

没有更多推荐了,返回首页