snprintf和sprintf区别分析
今天在项目中使用snprintf时遇到一个比较迷惑的问题,追根溯源了一下,在此对sprintf和snprintf进行一下对比分析。
因为sprintf可能导致缓冲区溢出问题而不被推荐使用,所以在项目中我一直优先选择使用snprintf函数,虽然会稍微麻烦那么一点点。这里就是sprintf和snprintf最主要的区别:snprintf通过提供缓冲区的可用大小传入参数来保证缓冲区的不溢出,如果超出缓冲区大小则进行截断。但是对于snprintf函数,还有一些细微的差别需要注意。
snprintf函数的返回值
sprintf函数返回的是实际输出到字符串缓冲中的字符个数,包括null结束符。而snprintf函数返回的是应该输出到字符串缓冲的字符个数,所以snprintf的返回值可能大于给定的可用缓冲大小以及最终得到的字符串长度。看代码最清楚不过了:
1
2
3
4
5
|
char tlist_3[10] = {0}; int len_3 = 0; len_3 = snprintf(tlist_3,10, "this is a overflow test!\n" ); printf ( "len_3 = %d,tlist_3 = %s\n" ,len_3,tlist_3); |
上述代码段的输出结果如下:
1
|
len_3 = 25,tlist_3 = this is a |
所以在使用snprintf函数的返回值时,需要小心慎重,避免人为造成的缓冲区溢出,不然得不偿失。
snprintf函数的字符串缓冲
1
2
|
int sprintf ( char *str, const char *format, ...); int snprintf( char *str, size_t size, const char *format, ...); |
上面的函数原型大家都非常熟悉,我一直以为snprintf除了多一个缓冲区大小参数外,表现行为都和sprintf一致,直到今天遇上的bug。在此之前我把下面的代码段的两个输出视为一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
char tlist_1[1024] = {0},tlist_2[1024]={0}; char fname[7][8] = { "a1" , "b1" , "c1" , "d1" , "e1" , "f1" , "g1" }; int i = 0, len_1,len_2 = 0; len_1 = snprintf(tlist_1,1024, "%s;" ,fname[0]); len_2 = snprintf(tlist_2,1024, "%s;" ,fname[0]); for (i=1;i<7;i++) { len_1 = snprintf(tlist_1,1024, "%s%s;" ,tlist_1,fname[i]); len_2 = sprintf (tlist_2, "%s%s;" ,tlist_2,fname[i]); } printf ( "tlist_1: %s\n" ,tlist_1); printf ( "tlist_2: %s\n" ,tlist_2); |
可实际上得到的输出结果却是:
1
2
|
tlist_1: g1; tlist_2: a1;b1;c1;d1;e1;f1;g1; |
知其然就应该知其所以然,这是良好的求知态度,所以果断翻glibc的源代码去,不凭空想当然。下面用代码说话,这就是开源的好处之一。首先看snprintf的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
glibc-2.18/stdio-common/snprintf.c: 18 #include <stdarg.h> 19 #include <stdio.h> 20 #include <libioP.h> 21 #define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a) 22 23 /* Write formatted output into S, according to the format 24 string FORMAT, writing no more than MAXLEN characters. */ 25 /* VARARGS3 */ 26 int 27 __snprintf ( char *s, size_t maxlen, const char *format, ...) 28 { 29 va_list arg; 30 int done; 31 32 va_start (arg, format); 33 done = __vsnprintf (s, maxlen, format, arg); 34 va_end (arg); 35 36 return done; 37 } 38 ldbl_weak_alias (__snprintf, snprintf) |
使用_IO_vsnprintf函数实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
glibc-2.18/libio/vsnprintf.c: 94 int 95 _IO_vsnprintf (string, maxlen, format, args) 96 char *string; 97 _IO_size_t maxlen; 98 const char *format; 99 _IO_va_list args; 100 { 101 _IO_strnfile sf; 102 int ret; 103 #ifdef _IO_MTSAFE_IO 104 sf.f._sbf._f._lock = NULL; 105 #endif 106 107 /* We need to handle the special case where MAXLEN is 0. Use the 108 overflow buffer right from the start. */ 109 if (maxlen == 0) 110 { 111 string = sf.overflow_buf; 112 maxlen = sizeof (sf.overflow_buf); 113 } 114 115 _IO_no_init (&sf.f._sbf._f, _IO_USER_LOCK, -1, NULL, NULL); 116 _IO_JUMPS (&sf.f._sbf) = &_IO_strn_jumps; 117 string[0] = '\0' ; 118 _IO_str_init_static_internal (&sf.f, string, maxlen - 1, string); 119 ret = _IO_vfprintf (&sf.f._sbf._f, format, args); 120 121 if (sf.f._sbf._f._IO_buf_base != sf.overflow_buf) 122 *sf.f._sbf._f._IO_write_ptr = '\0' ; 123 return ret; 124 } |
关键点出来了,源文件第117行string[0] = '\0';把字符串缓冲先清空后才进行实际的输出操作。那sprintf是不是就没有清空这个操作呢,继续代码比较中,sprintf的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
glibc-2.18/stdio-common/snprintf.c: 18 #include <stdarg.h> 19 #include <stdio.h> 20 #include <libioP.h> 21 #define vsprintf (s, f, a) _IO_vsprintf (s, f, a) 22 23 /* Write formatted output into S, according to the format string FORMAT. */ 24 /* VARARGS2 */ 25 int 26 __sprintf ( char *s, const char *format, ...) 27 { 28 va_list arg; 29 int done; 30 31 va_start (arg, format); 32 done = vsprintf (s, format, arg); 33 va_end (arg); 34 35 return done; 36 } 37 ldbl_hidden_def (__sprintf, sprintf ) 38 ldbl_strong_alias (__sprintf, sprintf ) 39 ldbl_strong_alias (__sprintf, _IO_sprintf) |
使用_IO_vsprintf而不是_IO_vsnprintf函数,_IO_vsprintf函数实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
glibc-2.18/libio/iovsprintf.c: 27 #include "libioP.h" 28 #include "strfile.h" 29 30 int 31 __IO_vsprintf ( char *string, const char *format, _IO_va_list args) 32 { 33 _IO_strfile sf; 34 int ret; 35 36 #ifdef _IO_MTSAFE_IO 37 sf._sbf._f._lock = NULL; 38 #endif 39 _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL); 40 _IO_JUMPS (&sf._sbf) = &_IO_str_jumps; 41 _IO_str_init_static_internal (&sf, string, -1, string); 42 ret = _IO_vfprintf (&sf._sbf._f, format, args); 43 _IO_putc_unlocked ( '\0' , &sf._sbf._f); 44 return ret; 45 } 46 ldbl_hidden_def (__IO_vsprintf, _IO_vsprintf) 47 48 ldbl_strong_alias (__IO_vsprintf, _IO_vsprintf) 49 ldbl_weak_alias (__IO_vsprintf, vsprintf ) |
在40行到42行之间没有进行字符串缓冲的清空操作,一切了然。
一开始是打算使用gdb调试跟踪进入snprintf函数探个究竟的,可是调试时发现用step和stepi都进不到snprintf函数里面去,看了一下链接的动态库,原来libc库已经stripped掉了:
1
2
3
4
5
6
7
8
|
hong@ubuntu:~ /test/test-example $ ldd snprintf_test linux-gate.so.1 => (0xb76f7000) libc.so.6 => /lib/i386-linux-gnu/libc .so.6 (0xb7542000) /lib/ld-linux .so.2 (0xb76f8000) hong@ubuntu:~ /test/test-example $ file /lib/i386-linux-gnu/libc .so.6 /lib/i386-linux-gnu/libc .so.6: symbolic link to `libc-2.15.so' lzhong@ubuntu:~ /test/test-example $ file /lib/i386-linux-gnu/libc-2 .15.so /lib/i386-linux-gnu/libc-2 .15.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=0x7a6dfa392663d14bfb03df1f104a0db8604eec6e, for GNU /Linux 2.6.24, stripped |
所以只能去找 ftp://ftp.gnu.org/gnu/glibc官网啃源代码了。
在找glibc源码时,我想知道系统当前使用的glibc版本,一时不知道怎么查,Google一下大多数都是Redhat上的rpm查法,不适用于Ubuntn,而用dpkg和aptitude show都查不到glibc package,后来才找到ldd用法。
1
2
3
4
5
6
|
hong@ubuntu:~ /test/test-example $ ldd --version ldd (Ubuntu EGLIBC 2.15-0ubuntu20) 2.15 Copyright (C) 2012 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Written by Roland McGrath and Ulrich Drepper. |
现在才发现Ubuntn用的是好像是EGLIBC,而不是标准的glibc库。其实上面ldd snprintf_test查看应用程序的链接库的方法可以更快速地知道程序链接的glibc版本。
snprintf和sprintf区别分析的更多相关文章
- C++中关于[]静态数组和new分配的动态数组的区别分析
这篇文章主要介绍了C++中关于[]静态数组和new分配的动态数组的区别分析,很重要的概念,需要的朋友可以参考下 本文以实例分析了C++语言中关于[]静态数组和new分配的动态数组的区别,可以帮助大家加 ...
- Java中Comparable和Comparator接口区别分析
Java中Comparable和Comparator接口区别分析 来源:码农网 | 时间:2015-03-16 10:25:20 | 阅读数:8902 [导读] 本文要来详细分析一下Java中Comp ...
- Oracle nvchar2和varchar2区别分析
Oracle nvchar2和varchar2区别分析: [注意]VARCHAR2是Oracle提供的特定数据类型,Oracle可以保证VARCHAR2在任何版本中该数据类型都可以向上和向下兼容.VA ...
- jQuery中的.bind()、.live()和.delegate()之间区别分析
jQuery中的.bind()..live()和.delegate()之间区别分析,学习jquery的朋友可以参考下. DOM树 首先,可视化一个HMTL文档的DOM树是很有帮助的.一个简单的 ...
- jQuery中的bind() live() delegate()之间区别分析
jQuery中的bind() live() delegate()之间区别分析 首先,你得要了解我们的事件冒泡(事件传播)的概念,我先看一张图 1.bind方式 $('a').bind('click', ...
- addEventListener()及attachEvent()区别分析
Javascript 的addEventListener()及attachEvent()区别分析 Mozilla中: addEventListener的使用方式: target.addEventLis ...
- C# Parse和Convert的区别分析
原文:C# Parse和Convert的区别分析 大家都知道在进行类型转换的时候有连个方法供我们使用就是Convert.to和*.Parse,但是疑问就是什么时候用C 什么时候用P 通俗的解释大家都知 ...
- jquery中attr和prop的区别分析
这篇文章主要介绍了jquery中attr和prop的区别分析的相关资料,需要的朋友可以参考下 在高版本的jquery引入prop方法后,什么时候该用prop?什么时候用attr?它们两个之间有什么区别 ...
- ql语句中left join和inner join中的on与where的区别分析
sql语句中left join和inner join中的on与where的区别分析 关于SQL SERVER的表联接查询INNER JOIN .LEFT JOIN和RIGHT JOIN,经常会用到 ...
随机推荐
- 谈谈MySQL 索引
1.索引是什么 索引(Index)是帮助MySQL高效获取数据的数据结构.我们可以简单理解为:索引的目的在于提高查询效率. 2.原理 索引的数据结构是B+树,原理图如下 关于B+树的详细介绍,可以参见 ...
- Asp.Net Core入门之配置文件
ASP.NET Core配置框架已内建支持 JSON.XML 和 INI 配置文件,内存配置(直接通过代码设置值),环境变量配置等方式配置参数. 本文主要和大家讲一下我们在项目中常用的以配置文件的方式 ...
- Mysql的一次查询的过程
1.用户发起请求,这里往往时多线程并发访问 2.去数据库线程池拿数据库链接,如果没有线程池,每次访问都要和数据库建立一次连接,非常耗时,效率低下 3.数据库层面上来说,可能会有多个系统同时访问它,所以 ...
- 7.SortSet排序集合类型操作
Sort Set排序集合类型 (1)介绍 和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个权.通过权值可以有序的获取集合中的元素 该Sort Set类型适合 ...
- MRCTF 2020 WP
MRCTF 2020 WP 引言 周末趁上课之余,做了一下北邮的CTF,这里记录一下做出来的几题的WP ez_bypass 知识点:MD5强类型比较,is_numeric()函数绕过 题目源码: I ...
- Java IO(一)概述
Java IO(一)概述 一.IO概述 (一).介绍 在Java中,所有的数据都是通过流读写的,Java提供了IO来处理设备之间的数据传输,Java程序中,对于数据的输入/输出操作 都是以“流”的方式 ...
- 一文彻底搞懂BERT
一.什么是BERT? 没错下图中的小黄人就是文本的主角Bert ,而红色的小红人你应该也听过,他就是ELMo.2018年发布的BERT 是一个 NLP 任务的里程碑式模型,它的发布势必会带来一个 NL ...
- 04 . 前端之JQuery
JQuery简介 # 1. jQuery是一个轻量级的.兼容多浏览器的JavaScript库.# 2. jQuery使用户能够更方便地处理HTML Document.Events.实现动画效果.方便地 ...
- 数据分析 | 数据可视化图表,BI工具构建逻辑
本文源码:GitHub·点这里 || GitEE·点这里 一.数据可视化 1.基础概念 数据可视化,是关于数据视觉表现形式的科学技术研究.其中,这种数据的视觉表现形式被定义为,一种以某种概要形式抽取出 ...
- JAVASE(十六) IO流 :File类、节点流、缓冲流、转换流、编码集、对象流
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.File类型 1.1.File类的理解 File类是在java.io包下 File可以理解成一个文件 ...