长久以来,在大多数 C 实现上,snprintf 都是作为一个非标准的扩展存在的。随着 C99 标准的颁布,snprintf 终于浮上台面而成为合法功能,目前 snprintf 已经是 C99 标准中的正式一员。不过,除非你的编译器是符合 C99 标准的,否则可能仍然必须使用供应商提供的非标准扩展,如 _snprintf 。
坦白地说,早该使用 snprintf 来取代 sprintf 了,即使在 snprintf 还没有标准化之前。大多数良好的编码标准都不推荐你使用像 sprintf 这样的不检查长度的函数,而且该原则是很有道理的。使用不做检查的 sprintf 长久以来会引起一些声名狼藉的常见问题,它通常会导致程序崩溃,尤其会导致安全脆弱问题。
借助于 snprintf,我们就可以正确编写刚才一直试图实现的带长度检查的 PrettyFormat() 版本。
// 示例 3-1:在 C 中使用 snprintf 来字符串化某些数据
//
void PrettyFormat(int i, char* buf, int buflen) {
// 这就是代码,简洁优雅,关键是比以前要安全得多:
snprintf(buf, buflen, "%4d", i);
}
注意,即便这样做了,仍然还存在另一种出错的可能,即调用者将缓冲区长度搞错了。这意味着跟那些具有资源管理功能的替代方案相比,snprintf 还算不上百分之百地杜绝缓冲区溢出可能性,不过跟 sprintf 相比它显然要安全多了,在“长度是否安全?”这个问题上应该算是合格的。使用 sprintf 没有合适的途径来绝对避免缓冲区溢出,而通过 snprintf,我们则可以(很大程度上)杜绝缓冲区溢出。
注意,snprintf 的一些标准化之前版本的行为稍有不同。尤其是在一个主要实现中,如果输出结果填满或者大于缓冲区容量,缓冲区里的串就不会以 '\0' 结尾。这种情况下,我们的 PrettyFormat() 函数就得稍作调整以应付这种非标准的行为:// 在C中使用一个并不十分遵从C99标准的_snprintf来将数据字符串化
//
void PrettyFormat(int i, char* buf, int buflen) {
// 这里是代码,简洁优雅,而且安全得多
if(buflen > 0) {
_snprintf(buf, buflen-1, "%4d", i);
buf[buflen-1] = '\0';
}
}
C++11,先前被称作 C++0x,即 ISO/IEC 14882:2011,是目前的 C++ 编程语言的正式标准。它取代第二版标准 ISO/IEC 14882:2003(第一版 ISO/IEC 14882:1998 公开于 1998 年,第二版于 2003 年更新,分别通称C++98 以及 C++03,两者差异很小)。
参考文章:1.《sprintf_s与_snprintf与_snprintf_s》
2.《snprintf函数使用(Windows与Linux版本)》
3.《snprintf、stringstream、strstream以及boost::lexical_cast的对比分析》
4. 网页
vs2010 中没有 snprintf 函数,但提供了 _snprintf,因为 snprintf 是 c99 的一部分,微软没有支持 c99,转而支持 c++11,而 _snprintf 是 c++11 的一部分。
The glibc implementation of the functions snprintf() and vsnprintf() conforms to the C99 standard
在 glibc 实现中支持的 snprintf() 和 vsnprintf() 均符合 C99 标准。
===============
linux 下 glibc 实现了符合 C99 标准的 snprintf(...) 。
int snprintf(char *str, size_t size, const char *format, ...);
windows 下 VS2010 实现了符合 C++11 标准的 _snprintf(...)。
int _snprintf(char *buffer, size_t count, const char *format[, argument]...);
最常见的错误用法有:
1.
char sa[256]={0};
_snprintf(sa,sizeof(sa),"%s",sb);
错误原因:当 sb 的长度 >= 256 的时候,sa 将没有 '\0' 结尾。
2.char sa[256];
_snprintf(sa,sizeof(sa)-1,"%s",sb);
错误原因:当 sb 的长度 >= 255 的时候,sa 将没有 '\0' 结尾,忘记给 sa 初始化。
3.char sa[256];
_snprintf(sa,sizeof(sa)-1,"%s",sb);
sa[sizeof(sa)]=0;
错误原因:最后一行数组越界。
正确的用法:1. //推荐用法
char sa[256];
sa[sizeof(sa)-1]=0;
_snprintf(sa,sizeof(sa),"%s",sb);
if(sa[sizeof(sa)-1]!=0)
{
printf("warning:string will be truncated");
sa[sizeof(sa)-1]=0;
}
2.
char sa[256]={0};
int result = _snprintf(sa,sizeof(sa),"%s",sb);
if(result==sizeof(sa) || result<0)
{
printf("warning:sting will be truncated");
sa[sizeof(sa)-1]=0;
}
个人第二种方法较好!
===============snprintf 函数并不是标准 c/c++ 中规定的函数,但是在许多编译器中,厂商提供了其实现的版本。在 gcc 中实现称为 snprintf,而在 VC 中实现为 _snprintf。由于不是标准函数,故没有一个统一的标准来规定该函数的行为,所以导致了各厂商间的实现版本可能会有差异。
函数定义为:
int _snprintf( char *buffer, size_t count, const char *format [, argument]... );
差异就发生在 count 参数。
在 VC 中,参数 count 是要写入的字符串的总字符数。
#include <stdio.h>
#include <string.h>
int main()
{
char str[5];
memset(str,0,sizeof(str));
int rt = _snprintf(str,3,"%s","abcdefg");
printf("%d\n",rt);
printf("%s",str);
return 0;
}
vc 程序的输出是:
-1
abc
在 Gcc 中,参数 count 是要向 buff 中写入 3 个字符,包括 '\0' 字符。
#include <stdio.h>
#include <string.h>
int main()
{
char str[5];
memset(str,0,sizeof(str));
int rt = snprintf(str,3,"%s","abcdefg");
printf("%d\n",rt);
printf("%s",str);
return 0;
}
gcc 程序的输出是:
7
ab
从输出结果可以知道:
- VC 中的 _snprintf 的 count 参数表示,会向 buff 中写入 count 个字符,不包括 '\0' 字符,并且不会在字符串末尾添加 '\0' 符。而且字符串长度超过参数 count 时,函数返回 -1,以表示可能导致错误;
- gcc 中的 snprintf 函数的 count 参数表示,向 buff 中写入 count 个字符,包括 '\0' 字符,并且返回实际的字符串长度。