【整理】snprintf辨析


      长久以来,在大多数 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' 字符,并且返回实际的字符串长度。

转载于:https://my.oschina.net/moooofly/blog/183817

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值