0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

嵌入式物联网开发 来源:嵌入式物联网开发 作者:嵌入式物联网开发 2022-08-31 13:18 次阅读

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等等。通过本文的方法,读者也可以写一小段测试代码,好好捋一捋本文提及的这几组函数,一起领悟下其他的奥秘和使用风险吧。

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

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7604

    浏览量

    136718
  • 函数
    +关注

    关注

    3

    文章

    4329

    浏览量

    62578
  • sprintf
    +关注

    关注

    0

    文章

    6

    浏览量

    4020
收藏 人收藏

    评论

    相关推荐

    C语言进阶】面试题:请使用宏定义实现字节对齐

    C语言进阶】面试题:请使用宏定义实现字节对齐
    的头像 发表于 07-11 09:21 2789次阅读
    【<b class='flag-5'>C</b><b class='flag-5'>语言</b><b class='flag-5'>进阶</b>】面试题:请使用宏定义实现字节对齐

    嵌入式C语言进阶之道

    嵌入式C语言进阶之道
    发表于 08-20 16:02

    c语言之高手进阶

    c语言之高手进阶 从点滴开始 杨帆起航
    发表于 07-04 16:14

    C语言进阶

    C语言进阶见附件
    发表于 08-13 15:51

    C语言进阶书分享!

    挺好的。c语言进阶.pdf (1.78 MB )
    发表于 10-16 02:44

    sprintf与printf函数的区别

    单片机中Sprint函数:说明1:使用该函数时必须包含stdio.h头文件,否则容易卡死程序说明2:sprintf与printf函数的区别:二者功能相似,但是sprintf函数打印到字符串中(将数值
    发表于 08-23 06:18

    单片机IO扩展(进阶)程序集合【C语言

    单片机IO扩展(进阶)程序集合【C语言】。
    发表于 01-06 11:04 23次下载

    sprintf和printf的区别

    的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要的字符串。那么接下来我们一起了解一下sprintf与printf的区别
    发表于 11-28 14:41 1.7w次阅读

    C51单片机C语言与标准C语言有什么区别

    一:C51(单片机C语言)与标准C语言区别1、 C
    发表于 10-09 08:00 134次下载
    <b class='flag-5'>C</b>51单片机<b class='flag-5'>C</b><b class='flag-5'>语言</b>与标准<b class='flag-5'>C</b><b class='flag-5'>语言</b>有什么<b class='flag-5'>区别</b>?

    C语言进阶学习课件资料合集

    本文档的主要内容详细介绍的是C语言进阶学习课件资料合集包括了:第1节-数据的存储,第2节-指针的进阶,第3节-字符串+内存函数的介绍,第4节-自定义类型详解(结构体+枚举+联合),第
    发表于 07-14 08:00 11次下载
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>的<b class='flag-5'>进阶</b>学习课件资料合集

    C语言进阶】“数组指针”和“指针数组”都是啥跟啥?

    C语言进阶】“数组指针”和“指针数组”都是啥跟啥?
    的头像 发表于 08-31 13:21 1912次阅读

    C语言进阶C语言指针的高阶用法

    C语言进阶C语言指针的高阶用法
    的头像 发表于 08-31 13:24 2320次阅读

    C语言进阶】利用assert高效排查你的C程序

    C语言进阶】利用assert高效排查你的C程序
    的头像 发表于 08-31 13:27 2112次阅读

    C语言进阶之嵌入式系统高级C语言编程

    电子发烧友网站提供《C语言进阶之嵌入式系统高级C语言编程.rar》资料免费下载
    发表于 11-18 10:32 1次下载
    <b class='flag-5'>C</b><b class='flag-5'>语言</b><b class='flag-5'>进阶</b>之嵌入式系统高级<b class='flag-5'>C</b><b class='flag-5'>语言</b>编程

    PLC编程语言C语言区别

    在工业自动化和计算机编程领域中,PLC(可编程逻辑控制器)编程语言C语言各自扮演着重要的角色。尽管两者都是编程语言,但它们在多个方面存在显著的区别
    的头像 发表于 06-14 17:11 2790次阅读