【整理】snprintf辨析

      长久以来,在大多数 C 实现上,snprintf 都是作为一个非标准的扩展存在的。随着 C99 标准的颁布,snprintf 终于浮上台面而成为合法功能,目前 snprintf 已经是 C99 标准中的正式一员。不过,除非你的编译器是符合 C99 标准的,否则可能仍然必须使用供应商提供的非标准扩展,如 _snprintf 。 

      坦白地说,早该使用 snprintf 来取代 sprintf 了,即使在 snprintf 还没有标准化之前。大多数良好的编码标准都不推荐你使用像 sprintf 这样的不检查长度的函数,而且该原则是很有道理的。使用不做检查的 sprintf 长久以来会引起一些声名狼藉的常见问题,它通常会导致程序崩溃,尤其会导致安全脆弱问题。 

借助于 snprintf,我们就可以正确编写刚才一直试图实现的带长度检查的 PrettyFormat() 版本。 

?


1

2

3

4

5

6

// 示例 3-1:在 C 中使用 snprintf 来字符串化某些数据

//

void PrettyFormat(int i, char* buf, int buflen) {

    // 这就是代码,简洁优雅,关键是比以前要安全得多:

    snprintf(buf, buflen, "%4d", i);

}

       注意,即便这样做了,仍然还存在另一种出错的可能,即调用者将缓冲区长度搞错了。这意味着跟那些具有资源管理功能的替代方案相比,snprintf 还算不上百分之百地杜绝缓冲区溢出可能性,不过跟 sprintf 相比它显然要安全多了,在“长度是否安全?”这个问题上应该算是合格的。使用 sprintf 没有合适的途径来绝对避免缓冲区溢出,而通过 snprintf,我们则可以(很大程度上)杜绝缓冲区溢出。

      注意,snprintf 的一些标准化之前版本的行为稍有不同。尤其是在一个主要实现中,如果输出结果填满或者大于缓冲区容量,缓冲区里的串就不会以 '\0' 结尾。这种情况下,我们的 PrettyFormat() 函数就得稍作调整以应付这种非标准的行为: 

?


1

2

3

4

5

6

7

8

9

// 在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. 

?


1

2

char sa[256]={0};

_snprintf(sa,sizeof(sa),"%s",sb);

错误原因:当 sb 的长度 >= 256 的时候,sa 将没有 '\0' 结尾。

2. 

?


1

2

char sa[256];

_snprintf(sa,sizeof(sa)-1,"%s",sb);

错误原因:当 sb 的长度 >= 255 的时候,sa 将没有 '\0' 结尾,忘记给 sa 初始化。

3. 

?


1

2

3

char sa[256];

_snprintf(sa,sizeof(sa)-1,"%s",sb);

sa[sizeof(sa)]=0;

错误原因:最后一行数组越界。

正确的用法: 

1. //推荐用法 

?


1

2

3

4

5

6

7

8

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.

?


1

2

3

4

5

6

7

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 是要写入的字符串的总字符数。 

?


1

2

3

4

5

6

7

8

9

10

11

#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

2

-1

abc

在 Gcc 中,参数 count 是要向 buff 中写入 3 个字符,包括 '\0' 字符。

?


1

2

3

4

5

6

7

8

9

10

11

#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 程序的输出是:

?


1

2

7

ab

从输出结果可以知道:

  • VC 中的 _snprintf 的 count 参数表示,会向 buff 中写入 count 个字符,不包括 '\0' 字符,并且不会在字符串末尾添加 '\0' 符。而且字符串长度超过参数 count 时,函数返回 -1,以表示可能导致错误;
  • gcc 中的 snprintf 函数的 count 参数表示,向 buff 中写入 count 个字符,包括 '\0' 字符,并且返回实际的字符串长度。
时间: 2025-01-01 12:44:06

【整理】snprintf辨析的相关文章

京东区域表整理

目前京东API文档里貌似还没开放京东配送区域查询的相应接口,所以需要自己动手了.通过对京东订单提交页显示的区域信息抓取,得到所有省份,城市,县区信息,再进行了一下整理,得到了这份京东区域表脚本,希望对做京东店铺App接入的人有帮助.打包下载

如何整理IE浏览器收藏夹

  第一步.在Windows 7系统下,打开IE9以后,依次单击"收藏夹"按钮."添加到收藏夹"按钮旁边的箭头和"整理收藏夹". 第二步.在"整理收藏夹"对话框中,将显示收藏夹链接和文件夹列表.从这里可以进行如下操作: 打开文件夹:单击文件夹将其展开,然后查看其包含的链接. 创建新文件夹:单击"新建文件夹",键入新文件夹的名称,然后按 Enter. 移动收藏夹:单击链接或文件夹,然后将其拖动到新的位置或文件

Work Like Alibaba系列分享资料整理(含PDF、视频、文字):持续更新中

阿里巴巴逐年增加的双11营业额和财年集团收入的背后隐藏着怎样的秘密?这18年来,它又是用怎样的工作方法.工作形式打造出高效.创新的企业帝国.带你全方位了解阿里的开发.产品.运营.销售是怎样协同.工作,我们还会邀请企业来实战分享,他们怎样Work Like Alibaba?取得了怎样的成果? Work like alibaba通过线下沙龙.线上直播.内容输出三个维度,携手阿里云的典型企业用户,联合阿里云.钉钉阿里产品,将阿里的前沿产品技术理念.敏捷研发模式.智能运维方法.智能办公.移动办公等渐渐渗

网站数据收集、整理、分析的方法和技巧

中介交易 SEO诊断 淘宝客 云主机 技术大厅 任务要求: 2010年10月18日中午12:00,入门任务第二期的任务开始,任务如下,对当前网络下的所有链接平台进行数据的收集整理和分析. 任务目的: 这次任务主要是锻炼我们的是几个力:执行力,耐力,分析能力,吃苦能力,这个是考验一个人工作是不是有耐心,会不会去动脑做事情,在收集的过程中,总是复制,粘贴这些枯燥无味并且重复性的工作,也是对一个人体力和眼力的考验.这个工作在第一个任务中也有对这方面的考验. 收集平台和整理平台可以说是另外一个力:苦力,

Python回顾与整理7:文件和输入输出

0.说明                  主要是下面的内容: 文件对象:内建函数.内建方法.属性 标准文件 文件系统:访问方法.文件执行 持久化存储 标准库中与文件有关的模块 1.文件对象         文件对象是用来访问文件的接口,而文件只是连续的字节序列,数据的传输经常会用到字节流,无论字节流是由单个字节还是大块数据组成. 2.文件内建函数(open()和file())         内建函数open()以及file()提供了初始化输入/输出(I/O)操作的通用接口,如果打开文件成功,

如何整理win7系统任务栏图标提高上网速度

  在windows系统任务栏中主要由开始菜单.应用程序区.语言栏选项和托盘区组成,任务栏的使用对我们操作有很大的帮助,如果在任务栏程序区创建太多的软件图标反而会影响到操作,win7 64位旗舰版下巧妙整理任务栏图标能够大大的提高工作效率. 提高工作效率方法/步骤: 1.看到这么多图标,"任务栏"是不是很累. 2.删除左边那些无用的图标.右键点击图标,单击"将此程序从任务栏解除"选项,这样删除工作完成了. 3.添加自己需要的程序图标.打开程序后在程序图标上面点击右键

IOS开发--常用工具类收集整理(Objective-C)(持续更新)

 前言:整理和收集了IOS项目开发常用的工具类,最后也给出了源码下载链接. 这些可复用的工具,一定会给你实际项目开发工作锦上添花,会给你带来大大的工作效率. 重复造轮子的事情,除却自我多练习编码之外,就不要傻傻的重复造轮子了,还是提高工作效率,早点完成工作早点回家陪老婆孩子. 所以下面备份的常用工具类一定是你需要的. 前提:你有一定的开发经验,知道它们在开发的什么地方需要,你都不知道用在哪里,那你需要个毛啊,还是好好另外学好基础吧.少儿不宜,请离开哦. 插件目录列表:(持续更新和添加) 1.UI

java泛型type体系整理

一直对jdk的ref使用比较模糊,早上花了点时间简单的整理了下,也帮助自己理解一下泛型的一些处理.   java中class,method,field的继承体系     java中所有对象的类型定义类Type   说明:    Type :  Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, 

整理对Spark SQL的理解

Catalyst Catalyst是与Spark解耦的一个独立库,是一个impl-free的执行计划的生成和优化框架. 目前与Spark Core还是耦合的,对此user邮件组里有人对此提出疑问,见mail.   以下是Catalyst较早时候的架构图,展示的是代码结构和处理流程. Catalyst定位 其他系统如果想基于Spark做一些类sql.标准sql甚至其他查询语言的查询,需要基于Catalyst提供的解析器.执行计划树结构.逻辑执行计划的处理规则体系等类体系来实现执行计划的解析.生成.