sprintf, snprintf, _snprintf, sprintf_s 等的区别

12 篇文章 0 订阅

先放结论

1.在支持snpritf的编译器 ,只使用int snprintf( char *buffer, size_t count, const char *format [, argument] ... );

无论成功或者失败,都会返回字符串的总长度(不包括结束符),如果buffer的大小不足以存放,那么就会截断。buffer里格式化的字符串带结束符。

2.在旧版本的Visual Studio例如vs2005, 不支持snprintf函数。

使用int _snprintf_s( char *buffer, size_t sizeOfBuffer, size_t count, const char *format [, argument] ... );

用法: sizeOfBuffer参数永远填"缓冲区的大小-1", count参数永远填_TRUNCATE,  

char buf[4];
int sp_ret = _snprintf_s(buf, sizeof(buf) - 1, _TRUNCATE, "1234%d", 5);

成功则返回字符串长度, 如果buffer大小不足以存放字符串,则返回-1,字符串会截断保存在buffer里,带字符串结束符。

上面例子返回-1, buf里的数据是"123\0"。

 

总结

1.sprintf,参数不带缓冲区长度, 成功返回字符串长度, 格式化的字符串带结束符, 编译器都支持。 越界了程序不会立刻崩溃,难以发现。

2.snprintf,  参数带缓冲区长度,成功返回字符串长度, 格式化的字符串带结束符, 缓冲区不足时截断字符串,同时返回格式化字符串需要的buffer长度(不包括结束符)。 新版本的Visual Studio才支持,老版本无法使用。 安全。

3.sprintf_s, 参数带缓冲区长度,成功返回字符串长度,缓冲区不足时直接抛异常,程序崩溃。 新旧版本的Visual Studio都支持。 release版不建议使用,因为一旦缓冲区不足,异常没处理好,程序就挂了。

4._snprintfsnprintf差不多,唯一区别就是当字符串不算结束符的长度刚好跟缓冲区一样长的时候, 也会成功并把字符串写到缓冲区,这时候缓冲区是不带结束符的,建议用snprintf代替,避免后面的代码误以为字符串带结束符导致出错或者后面代码每次都要检查长度逻辑反而复杂化。

5.微软的一系列函数中,函数开头带_表示微软在c/c++标准以外的实现, _s的表示安全加强版本,参数错误例如缓冲区长度不足程序会抛异常,带w表示宽字符版本, 带_l的表示带locale_t参数的版本。

例如strcpy是标准里的函数, strcpy_s是安全加强版本, 虽然在c++11以后才有,但是老版本的Visual Studio就支持这个函数, 同样sprintf_ssprintf对应的安全版本。 在VS2015以后,不带安全版本的字符串函数都废弃(deprecated)了, 如果使用了,编译器会报警。

例如sprintf处理char字符串, 宽字符版本的字符串wchar_t就用swprintf, 同样地_snprintf对应_snwprintf, _snprintf_s对应_snwprintf_s

例如atof对应的多一个locale_t参数的函数是_atof_l,  _snprintf_s对应_snprintf_s_l_snwprintf_s对应_snwprintf_s_l

6.sprintf以及snprintf都是跨平台可用。其他的我测试只在Windows平台可用。另外snprintf属于C99的内容,在Visual Studio老版本不能使用,只能用_snprintf等代替。

反正使劲并且只用snprintf就对了。除非在Windows平台并且用老古董的Visual Studio。另外strcpystrcat等也换成strncpy, strncat(C99以后支持,并且不支持C99的VS老版本也支持)


1. int sprintf( char *buffer, const char *format [, argument] ... );

如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

例子A:缓冲区足够放下整个字符串

char buf[4];
int sp_ret;
sp_ret = sprintf(buf, "12%d", 3);

成功, 返回3, 并且buf里的内容是"123\0" 一共4个字节。

例子B:缓冲区不足

char buf[4];
int sp_ret;
sp_ret = sprintf(buf, "123%d", 4);

返回值为4,内存越界,就是当时不死,也是迟早要完。


2. int snprintf( char *buffer, size_t count, const char *format [, argument] ... );

比sprinf多一个count参数,标识缓冲区的长度,避免越界,更加安全。

例子A:缓冲区足够放下整个字符串

char buf[4];
int sp_ret;
sp_ret = snprintf(buf, sizeof(buf), "12%d", 3);

成功,  返回3,  buf数据"123\0" 一共4个字节。 跟sprintf的表现差不多。

例子B:缓冲区不足

char buf[4];
int sp_ret;
sp_ret = snprintf(buf, sizeof(buf), "123%d", 4);

成功,返回4, buf数据"123\0" 一共4个字节。 目标字符串会被截断成buf刚好能放下的字符串(包括字符串结束符null terminator)。

char buf[4];
int sp_ret;
sp_ret = snprintf(buf, sizeof(buf), "1234%d", 5);

成功,返回5, buf数据"123\0" 一共4个字节。 目标字符串会被截断成buf刚好能放下的字符串(包括字符串结束符null terminator)。


3. int sprintf_s( char *buffer, size_t sizeOfBuffer, const char *format, ... );

跟sprintf对应,后面_s 代表 security, 是微软CRT函数针对sprintf函数的安全增强版,早在Vs2005版本就可以使用, 新版本的Visual Studio应该是2015以后,对sprintf函数标识为废弃,编译器就会提示警告,并且建议使用安全版本。 sprintf_s相比sprintf函数多了个sizeOfBuffer参数, 指定buffer的长度,避免越界。 

例子A:缓冲区充足

char buf[4];
int sp_ret;
sp_ret = sprintf_s(buf, sizeof(buf), "12%d", 3);

成功,  返回3,  buf数据"123\0" 一共4个字节。 

例子B:缓冲区不足(字符串结束符也要算)

char buf[4];
int sp_ret;
sp_ret = sprintf_s(buf, sizeof(buf), "123%d", 4);

这时候,程序直接抛异常。。。说好的更加安全,原来是抛异常尽早发现问题,就是如此的暴力,且枯燥。 曾经有个项目,在客户环境跑60多天就会崩溃一次,最后发现是因为有个定时记录日志的地方,buffer缓冲区设置比较小, 随时时间推移,计数越来越大,终于60多天超过缓冲区导致进程崩了。就因为一个无关紧要的功能用了sprintf_s函数,导致整个程序崩溃, 同时崩溃的还有我弱小的心灵。 从此我再也不敢用_s的函数了。 很多时候,字符串格式化截断,还是一种可以接受的结果,例如发现日志,或者某个显示的字符串不完整,至少客户还能正常使用,程序员也勉强能看出缓冲区不足的问题,尽快修复。 因此我比较倾向于使用snprintf代替。

同理, strcpy_s, strcat_s等,都比原来的strcpy, strcat函数多了一个缓冲区长度参数,也是出错直接抛异常。建议使用strncpy, strncat代替。

 

template <size_t size>

int sprintf_s( char (&buffer)[size], const char *format, ... ); // C++ only

这是sprintf_s在c++的另外一个重载版本, char数组,通过模板获取到缓冲区的大小,可以少填长度参数。 跟原版功能是一样的。


4. int _snprintf( char *buffer, size_t count, const char *format [, argument] ... );

snprintf前面的多下划线‘_‘’前缀, 在低版本Visual Studio(例如vs2005)不支持C99,  微软提供了一系列的'_'开头的函数, _snprintf, _strncpy, _strncat等。 用于对字符串缓冲区操作时,指定长度防止越界。 sprintf不指定缓冲区长度实在不太安全。 到了高版本的Visual Studio, 这些_开头的函数依旧可以使用。C99标准增加了strncpy, strncat等, 如果c++标准里有的函数,尽量用标准的。

例子A:缓冲区充足

char buf[4];
int sp_ret;
sp_ret = _snprintf(buf, sizeof(buf), "12%d", 3);

成功,  返回3,  buf数据"123\0" 一共4个字节。 跟snprintf的表现一致。

例子B:缓冲区刚好放下字符串,但不包括字符串结束符。

char buf[4];
int sp_ret;
sp_ret = _snprintf(buf, sizeof(buf), "123%d", 4);

成功,返回4,  buf数据"1234" 一共4个字节。 没有字符串结束符。

例子C:缓冲区连不包括结束符的字符串都放不下。

char buf[4];
int sp_ret;
sp_ret = _snprintf(buf, sizeof(buf), "1234%d", 5);

失败,返回-1。 buf数据"1234" 一共4个字节。没有结束符的字符串还是格式化到buf了。

 

在高版本的Visual Studio因为支持c++标准的std::snprintf, 建议直接使用std::snprintf代替。

在低版本的Visual Studio,在使用_snprintf的时候,为了确保字符串像std::snprintf一样截断。需要以下面方式使用。

char buf[4];
int sp_ret;
sp_ret = _snprintf(buf, sizeof(buf) - 1, "1234%d", 5);
if (sp_ret == -1) { // buf不足,截断
	buf[sizeof(buf) - 1] = '\0';
}

传入的sizeof(buf) - 1确保至少保留一个字节用于字符串结束符。

当返回-1的时候,表示buf前面的sizeof(buf) - 1都已填充满数据,这时候最后一个字节是没有结束符的,需要手动添加。

以上方式使用起来还是比较麻烦,低版本VS建议用_snprintf_s来代替

char buf[4];
int sp_ret;
sp_ret = _snprintf_s(buf, sizeof(buf) - 1, sizeof(buf) - 1, "1234%d", 5);

或者

char buf[4];
int sp_ret;
sp_ret = _snprintf_s(buf, sizeof(buf) - 1, _TRUNCATE, "1234%d", 5);

5.int _snprintf_s( char *buffer, size_t sizeOfBuffer, size_t count, const char *format [, argument] ... );

这个函数可以理解为 _snprintf + _s, 也就是_snprintf函数一模一样的功能, _s 意味着,安全,也意味着超过buffer长度就会崩溃。该函数vs2005就能用。

count是指字符串的最大长度。

当count参数等于sizeOfBuffer时,_snprintf_s的功能跟sprintf_s一模一样, 要么正常,要么崩溃。

当count参数等于sizeOfBuffer - 1或者 _TRUNCATE时,_snprintf_ssnprintf的功能一模一样,两个函数成功都是返回字符串的长度,失败_snprintf_s返回-1, 而snprintf返回字符串需要的总长度。#define _TRUNCATE ((size_t)-1), _TRUNCATE是一个特殊标识,告诉该函数如果字符串超过范围则截断。

当count参数少于sizeOfBuffer - 1时, 相当于有些可用的buffer也故意不让使用,这种用法有些奇怪。应该没人会这么用吧。

template <size_t size>

int _snprintf_s( char (&buffer)[size], size_t count, const char *format [, argument] ... ); // C++ only

这是_snprintf_s在c++的另外一个重载版本, char数组,通过模板获取到缓冲区的大小,可以少填长度参数。 跟原版功能是一样的。


6.

int _sprintf_l( char *buffer, const char *format, locale_t locale [, argument] ... );

locale:The locale to use.  

由于国家/语言的差异, 有些用于格式化的函数的行为在不同语言里并不相同。

例如我们浮点数表示"3.5", 但是在德国,浮点数不是用'.'分隔,而是用','。因此在德语中,应该表示为“3,5”。

这样atof函数在两个国家的格式化结果应该是不一样的。因此微软准备了一系列的_XXX_l函数,比正常的函数多一个locale_t参数。例如_sprintf_l对应sprint, _atof_l对应atof, _atoi_l对应atoi。而默认的XXX函数实际上底层调用的是_XXX_l(locale=NULL)。

因此sprintf函数底层应该是调用了_sprintf_l(buffer, format, NULL, ...)。

atof(str)实际上调用了_atof_l(str, NULL),  atoi(str)实际上调用了_atoi_l(str, NULL)。

更多与地区相关的函数参考https://docs.microsoft.com/en-us/cpp/c-runtime-library/locale?view=msvc-150

同理

int _snprintf_s_l( char *buffer, size_t sizeOfBuffer, size_t count, const char *format, locale_t locale [, argument] ... );

相当于_snprintf_s + _l。

 

另外由于宽字符wchar_t的存在,每一个对char的字符串操作都有一个宽字符的对应版本的函数

宽字符以及locale两两组合,每个函数都会出现4个版本。 如果是_snprintf等有sizeOfBuffer参数的, 通过模板重载自动识别sizeOfBuffer,函数形式再多一倍。
 

sprintf:

int sprintf( char *buffer, const char *format [, argument] ... ); 
int _sprintf_l( char *buffer, const char *format, locale_t locale [, argument] ... );
int swprintf( wchar_t *buffer, size_t count, const wchar_t *format [, argument]... ); 
int _swprintf_l( wchar_t *buffer, size_t count, const wchar_t *format, locale_t locale [, argument] ... );
template <size_t size>
int sprintf( char (&buffer)[size], const char *format [, argument] ... );
template <size_t size> 
int _sprintf_l( char (&buffer)[size], const char *format, locale_t locale [, argument] ... ); 

 

sprintf_s:

int sprintf_s( char *buffer, size_t sizeOfBuffer, const char *format [, argument] ... );
int _sprintf_s_l( char *buffer, size_t sizeOfBuffer, const char *format, locale_t locale [, argument] ... );
int swprintf_s( wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format [, argument] ... );
int _swprintf_s_l( wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format, locale_t locale [, argument] ... );
template <size_t size>
int sprintf_s( char (&buffer)[size], const char *format [, argument] ... );
template <size_t size>
int swprintf_s( wchar_t (&buffer)[size], const wchar_t *format [, argument] ... );

 

snprintf:

int snprintf( char *buffer, size_t count, const char *format [, argument] ... );
int _snprintf( char *buffer, size_t count, const char *format [, argument] ... );
int _snprintf_l( char *buffer, size_t count, const char *format, locale_t locale [, argument] ... );
int _snwprintf( wchar_t *buffer, size_t count, const wchar_t *format [, argument] ... );
int _snwprintf_l( wchar_t *buffer, size_t count, const wchar_t *format, locale_t locale [, argument] ... );
template <size_t size>
int _snprintf( char (&buffer)[size], size_t count, const char *format [, argument] ... ); 
template <size_t size>
int _snprintf_l( char (&buffer)[size], size_t count, const char *format, locale_t locale [, argument] ... );
template <size_t size>
int _snwprintf( wchar_t (&buffer)[size], size_t count, const wchar_t *format [, argument] ... );
template <size_t size>
int _snwprintf_l( wchar_t (&buffer)[size], size_t count, const wchar_t *format, locale_t locale [, argument] ... );

 

snprintf_s:

int _snprintf_s( char *buffer, size_t sizeOfBuffer, size_t count, const char *format [, argument] ... ); 
int _snprintf_s_l( char *buffer, size_t sizeOfBuffer, size_t count, const char *format, locale_t locale [, argument] ... ); 
int _snwprintf_s( wchar_t *buffer, size_t sizeOfBuffer, size_t count, const wchar_t *format [, argument] ... ); 
int _snwprintf_s_l( wchar_t *buffer, size_t sizeOfBuffer, size_t count, const wchar_t *format, locale_t locale [, argument] ... );
template <size_t size>
int _snprintf_s( char (&buffer)[size], size_t count, const char *format [, argument] ... ); 
template <size_t size>
int _snwprintf_s( wchar_t (&buffer)[size], size_t count, const wchar_t *format [, argument] ... );

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值