网站首页

人工智能P2P分享搜索全网发布信息网站地图标签大全

当前位置:诺佳网 > 电子/半导体 > 嵌入式技术 >

【C语言进阶】sprintf和snprintf的区别

时间:2022-08-31 13:18

人气:

作者:admin

标签: 函数  sprintf  C语言 

导读:【C语言进阶】sprintf 和 snprintf 真的没有区别吗?...

C语言上总有些非常相近的接口函数,比如sprintf和snprintf就是其中的一对。以笔者多年的工作经验,这对接口函数在平时的编程中,使用的频度是非常高,只是你真的了解它们俩的区别吗?

带着这个问题,请跟随笔者的思路梳理一遍sprintf和snprintf。通过阅读本文,你将了解到以下内容:

sprintf和snpintf分别是什么?

sprintf和snprintf的区别与联系

sprintf和snprintf的使用秘诀


sprintf和snpintf分别是什么?


【sprintf】的函数原型如下所示:

/**
功能: 把格式化的数据写入某个字符串缓冲区
入参:format,输出字符串的格式化列表,比如"%s %d %c"等
入参: [argument],format对应的不定参数列表,与printf的不定入参类似
出参:buffer,指向一段存储空间,用于存储格式化之后的字符串
返回值:返回写入buffer 的字符数,出错则返回-1. 如果 buffer 或 format 是空指针,
且不出错而继续,函数将返回-1,并且 errno 会被设置为 EINVAL。
备注:它是个变参函数
*/
int sprintf( char *buffer, const char *format, [ argument] … );

【snprintf】的函数原型如下所示:

/**
功能: 有长度限制地,把格式化的数据写入某个字符串缓冲区
入参:format,输出字符串的格式化列表,比如"%s %d %c"等
入参: [argument],format对应的不定参数列表,与printf的不定入参类似
入参:size,表示buffer指向存储空间的大小
出参:buffer,指向一段存储空间,用于存储格式化之后的字符串
返回值:返回写入buffer 的字符数,出错则返回-1. 如果 buffer 或 format 是空指针,
且不出错而继续,函数将返回-1,并且 errno 会被设置为 EINVAL。
备注:它是个变参函数
*/
int snprintf( char *buffer, size_t size, const char *format, [ argument] … );

sprintf和snprintf的区别与联系


通过对比sprintf和snprintf的函数原型,我们可以发现两者其实完成相同功能的接口,都是将一段数据经格式化操作之后,转换成一段字符串,通过接口传入的buffer指针将格式化的字符串内容输出。

我们细细比对两个函数原型,我们会发现snprintf比sprintf多了一个表示buffer指针指向存储空间的大小的入参size,那么它到底有什么作用呢?我们先来分析下snprintf接口的内部行为与size的关系:

如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0')

如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为欲写入的字符串长度。

看完这一段解释之后,大概你就明白了,原来snprintf就是sprintf的安全版本,因为单从sprintf的内部行为来看,它是没有办法保证对buffer指针的赋值操作是没有越界的,因为它压根就不知道buffer的存储空间多少有多大,所以它只能认为是【无穷大】。但是snprintf通过入参size,恰好可以很好的解决这个问题,它可以很明确的告知snprintf的内部操作,以size作为界线,当输出的字符串长度要超过size时,应做出裁剪输出。在很多的编程宝典中,都是推荐使用snprintf,而要求编程者尽可能地避免使用sprintf这种不安全接口。


sprintf和snprintf的使用秘诀


我们通过一段测试代码来展示下两者的使用方法,以及上一小结中提及的可能导致buffer溢出的严重问题:

//sprintf的用法
{
    char buffer[10]; //定义一个只有10个字节空间的buffer数组
    const int a = 12345; //定义一个int型的常量
    const char *msg = "012345678901234567890"; //定义一个长度为20字节的字符串常量

    sprintf(buffer, "%d", a); //将a变量按int类型打印成字符串,输出到buffer中
    /*
    输出分析:
    输出结果: buffer="12345"
    因为最后输出的buffer内容长度不超过10字节,所以此时sprintf操作是没有溢出风险的
    */

    sprintf(buffer, "%d+%s", a, msg); //将a变量和msg字符串通过“+”连接成一个字符串
    /*
    输出分析:
    由于buffer只有10个字节空间,而sprintf在执行字符串格式化输出的时,并不知道buffer的真实长度,
    所以它会将"12345+012345678901234567890"这整个字符串都拷贝到buffer空间上,这就导致了buffer存储空间溢出了。
    从存储位置上分析,我们知道buffer空间属于一个栈空间,在它自己的10字节之外的空间很明显是其他栈变量的存储空间,
    一旦sprintf将10字节外的其他空间也操作了,这就有可能破坏了其他栈变量的内容,这有可能是致命的。
    */
}

//snprintf的用法
{
    char buffer[10]; //定义一个只有10个字节空间的buffer数组
    const int a = 12345; //定义一个int型的常量
    const char *msg = "012345678901234567890"; //定义一个长度为20字节的字符串常量

    snprintf(buffer, sizeof(buffer), "%d", a); //将a变量按int类型打印成字符串,输出到buffer中
    /*
    输出分析:
    输出结果: buffer="12345"
    因为最后输出的buffer内容长度不超过10字节,所以snprintf操作是没有溢出风险的;
    此种情况下,使用sprintf和snpintf都可以得到同样的结果,且都不会产生数组溢出。
    */

    sprintf(buffer, sizeof(buffer), "%d+%s", a, msg); //将a变量和msg字符串通过“+”连接成一个字符串
    /*
    输出分析:
    输出结果是: buffer="12345+0123",加上一个'\0'的字符串结束符,
    刚好占用了buffer的10字节的存储空间,不存在任何的buffer溢出风险。而"0123"后面的字符串都被snprintf内部裁剪掉了,这就体现了snprintf操作安全的特性。
    */
}

通过以上分析,我们很好地认识到了sprintf的操作是不安全的。在C语言的语法上,指针的灵活性也带来可能导致的指针溢出风险,而snprintf恰好就是解决了这个困惑的sprintf升级版本。

类似的,还有strcat和strncat、strcpy和strncpy等等。通过本文的方法,读者也可以写一小段测试代码,好好捋一捋本文提及的这几组函数,一起领悟下其他的奥秘和使用风险吧。

以上总结,均来自笔者多年的实践经验,如有发现不正确的陈述或错误的观点,还望读者指正,感激不尽。

审核编辑:汤梓红
温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信