文档章节

【整理】snprintf辨析

摩云飞
 摩云飞
发布于 2013/12/12 09:54
字数 1406
阅读 319
收藏 0

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

© 著作权归作者所有

共有 人打赏支持
摩云飞
粉丝 368
博文 534
码字总数 952694
作品 0
徐汇
程序员
私信 提问
snprintf和sprintf区别分析

今天在项目中使用snprintf时遇到一个比较迷惑的问题,追根溯源了一下,在此对sprintf和snprintf进行一下对比分析。 因为sprintf可能导致缓冲区溢出问题而不被推荐使用,所以在项目中我一直优...

水海云
2013/11/18
0
1
辨析三层架构MVC与MVP

辨析三层架构MVC与MVP 请前辈辨析~ thanks http://my.oschina.net/barter/blog/90855

JavaOlder
2013/01/21
354
0
谈谈snprintf

众所周知,sprintf不能检查目标字符串的长度,可能造成众多安全问题,所以都会推荐使用snprintf. snprintf(_snprintf)的声明是这样的 int _snprintf( char *buffer, size_t count, const char ...

长平狐
2013/01/06
74
0
snprintf()函数返回值

函数原型: int snprintf(char str, size_t size, const char format, ...); size 的作用就是限制往str写入不超过size个字节(包括了结尾的'0')。 因为sprintf()函数如果成功的话,返回成功写...

呼噜呼噜睡翻天
2012/12/14
0
0
linux c/c++ 面试题目整理(一)

1、求下面函数的返回值 问:假定x是9999,那么返回多少? 答:返回的是8,解题思路是将x转化为二进制,看含有多少个1,则就返回多少。 2、文件中有一组整数,要求排序后输出到另一个文件中 ...

晟夏的叶
2017/04/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

sql 开窗函数

开窗函数:在开窗函数出现之前存在着很多用 SQL 语句很难解决的问题,很多都要通过复杂的相关子查询或者存储过程来完成。为了解决这些问题,在 2003 年 ISO SQL 标准加入了开窗函数,开窗函数...

hblt-j
12分钟前
0
0
使用Vue动态生成form表单的实例代码

具有数据收集、校验和提交功能的表单生成器,包含复选框、单选框、输入框、下拉选择框等元素以及,省市区三级联动,时间选择,日期选择,颜色选择,文件/图片上传功能,支持事件扩展。 欢迎大家s...

嫣然丫丫丫
19分钟前
0
0
NEO区块链-DAPP开发直通车-第零篇

什么是DAPP DAPP 是以太坊发明的词汇 Decentralized Application. 目前基于区块链技术开发的应用程序广泛的接受使用了这一名称。 NEL将为开发DAPP提供全面的服务 什么是NEL NEL是 “NewEcon...

NEO-FANS
23分钟前
1
0
可视化软件VisIt在Ubuntu18.04上的安装

可视化软件VisIt在Ubuntu18.04上的安装 参考文档及使用说明 1.下载 在官网下载页面下载合适版本的安装文件,Ubuntu有专用的 https://wci.llnl.gov/simulation/computer-codes/visit/executa...

佚文
29分钟前
1
0
selenium之表格的定位

真的勇士, 敢于直面惨淡的warning、 敢于正视淋漓的error 目录 被测试网页的HTML代码 1.遍历表格所有单元格 2.定位表格中的某个元素 3.定位表格中的子元素 总结 浏览器网页常常会包含各类表...

程序猿拿Q
43分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部