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

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

3天内不再提示

关于你可能不知道的printf

黄工的嵌入式技术圈 来源:黄工的嵌入式技术圈 作者:黄工的嵌入式技术 2020-02-05 12:28 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前言

printf可能是我们在学习C语言的过程中最早接触的库函数了。其基本使用想必我们都已经非常清楚了。但是下面的这些情况你是否已经清楚地知道了呢?

示例程序

我们来看一个示例程序,看看你能否对下面的结果输出有非常清晰的认识。

#include intmain(void) { inta=4; intb=3; intc=a/b; floatd=*(float*)(&c); longlonge=0xffffffffffffffff; printf("a/b:%f,a:%d\n",a/b,a,b);//打印0 printf("(float)a/b:%f\n",((float)a)/b);//打印1 printf("(double)a/b:%lf\n",((double)a)/b);//打印2 printf("d:%f\n",d);//打印3 printf("%.*f\n",20,(double)a/b);//打印4 printf("e:%d,a:%d\n",e,a);//打印5 printf("a:%d,++a:%d,a++:%d\n",a,++a,a++);//打印6 return0; }

编译为32位程序:

gcc-m32-otesttest.c

在运行之前,你可以自己先猜想一下打印结果会是什么。实际运行结果:

a/b:0.000000,a:3//打印0的结果 (float)a/b:1.333333//打印1的结果 (double)a/b:1.333333//打印2的结果 d:0.000000//打印3的结果 1.33333333333333325932//打印4的结果 e:-1,a:-1//打印5的结果 a:6,++a:6,a++:4//打印6的结果

你的猜想是否都正确呢?如果猜想错误,那么接下来的内容你就不应该错过了。

你是否会有以下疑问:

0.打印0的a/b为什么不是1,a为什么不是4?

1.打印1和打印2有什么区别呢?

2.打印3为什么结果会是0.000000?

3.打印4的结果为什么最后的小数位不对?其中的*是什么意思?

4.打印5中,为什么a的值是-1而不是4?

5.打印6中,结果为什么分别是6,6,4?

在解答这些问题之前,我们需要先了解一些基本内容。

可变参数中的类型提升

printf是接受变长参数的函数,传入printf中的参数个数可以不定。而我们在变长参数探究中说到:
调用者会对每个参数执行“默认实际参数提升",提升规则如下:
——float将提升到double
——char、short和相应的signed、unsigned类型将提升到int

也就是说printf实际上只会接受到double,int,long int等类型的参数。而从来不会实际接受到float,char,short等类型参数。
我们可以通过一个示例程序来检验:

//badcode #include intmain(void) { char*p=NULL; printf("%d,%f,%c\n",p,p,p); return0; }

编译报错如下:

printf.c:Infunction‘main’: printf.c:5:12:warning:format‘%d’expectsargumentoftype‘int’,butargument2hastype‘char*’[-Wformat=] printf("%d,%f,%c\n",p,p,p); ^ printf.c:5:12:warning:format‘%f’expectsargumentoftype‘double’,butargument3hastype‘char*’[-Wformat=] printf.c:5:12:warning:format‘%c’expectsargumentoftype‘int’,butargument4hastype‘char*’[-Wformat=]

我们可以从报错信息中看到:

%d 期望的是 int 类型参数

%f 期望的是 double 类型参数

%c 期望的也是 int 类型参数

而编译之所以有警告是因为,char *类型无法通过默认实际参数提升,将其提升为int或double。

参数入栈顺序以及计算顺序

在C语言中,参数入栈顺序是确定的,从右往左。而参数的计算顺序却是没有规定的。也就是说,编译器可以实现从右往左计算,也可以实现从左往右计算。

浮点数的有效位

对于double类型,其有效位为15~~16位(参考:对浮点数的一些理解)。

可变域宽和精度

printf中,*的使用可实现可变域宽和精度,使用时只需要用*替换域宽修饰符和精度修饰符即可。在这样的情况下,printf会从参数列表中取用实际值作为域宽或者精度。示例程序如下:

#include intmain(void) { floata=1.33333333; char*p="hello"; printf("%.*f\n",6,a); printf("%*s\n",8,p); return0; }

运行结果:

1.333333 hello

而这里的6或者8完全可以是一个宏定义或者变量,从而做到了动态地格式控制。

格式控制符是如何处理参数的

printf有很多格式控制符,例如%d,它在处理输入时,会从堆栈中取其对应大小,即4个字节作为对应的参数值。也就是说,当你传入参数和格式控制符匹配或者在经过类型提升后和格式控制符匹配的时候,参数处理是没有任何问题的。但是不匹配时,可能会出现未定义行为(有两种情况例外,我们后面再说)。例如,%f期望一个double(8字节)类型,但是传入的参数是int(4字节),那么在处理这个int参数值,可能会多处理4个字节,并且也会造成处理数据错误。

真相大白

有了前面这些内容的铺垫,我们再来解答开始的疑问:

对于问题0,a/b的结果显然为4字节的int类型1,而%f期望的是8字节的double,而计算结果只有4个字节,因此会继续格式化后面4个字节的a,而整型1和后面a组合成的8字节数据,按照浮点数的方式解释时,它的值就是0.000000了。由于前面已经读取解释了a的内容,因此第二个%d只能继续读取4个字节,也就是b的值3,最终就会出现打印a的值是3,而不是4。

对于问题1,实际上在printf中,是不需要%lf的,%f期望的就是double类型,在编译最开始的示例程序其实就可以发现这个事实。当然了在scanf函数中,这两者是有区别的。

对于问题2,也很简单,2的二进制存储形式按照浮点数方式解释读取时,就是该值。

对于问题3,double的有效位为15~16位,也就是之外的位数都是不可靠的。printf中的*可用于实现可变域宽和精度,前面已经解释过了。

对于问题4,这里不给出,留给读者思考,欢迎大家可留言区给出原因。

对于问题5,虽然参数计算顺序没有规定,但是实际上至少对于gcc来说,它是从右往左计算的。也就是说,先计算a++,而a++是先用在加,即压入a=4,其后,a的值变为5;再计算++a,先加再用,即压入a=5+1=6;最后a=6,压入栈。最终从左往右压入栈的值就分别为6,6,4。也就是最终的打印结果。但是实际情况中,这样的代码绝对不该出现!

至此,真相大白。

总结

虽然我们前面解释了那些难以理解的现象,同时读者可以参考变长参数探究和对浮点数的一些理解找到更多的信息。但是我们在实际编程中应该注意以下几点:

格式控制符应该与对应参数类型匹配或者与类型提升后的参数类型匹配。

绝对避免出现计算结果与参数计算顺序有关的代码。

*在printf中实现可变域宽和精度。

printf不会实际接受到char,short和float类型参数。

如果%s对应的参数可能为NULL或者对应整型,那将是一场灾难。

不要忽略编译器的任何警告,除非你很清楚你在做什么。

例外情况指的是有符号整型和无符号整型之间,以及void*和char*之间。

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

    关注

    1

    文章

    68

    浏览量

    19323
  • 程序
    +关注

    关注

    117

    文章

    3849

    浏览量

    85493
  • Printf
    +关注

    关注

    0

    文章

    84

    浏览量

    14831
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    SMA接头老化了只能报废?分享几个不知道的维护回春术

    SMA 连接器性能下降只能报废?本文由德索精密工业(Dosin)技术专家分享四个鲜为人知的射频接口“回春术”。从高纯酒精深层除垢到阻抗稳定性恢复,深度解析如何通过物理维护延长高频跳线使用寿命。文章附带详细的维护效果评估表,助您在保障射频系统精度的同时,大幅降低 B 端实验室及工程现场的耗材成本。
    的头像 发表于 04-29 15:36 327次阅读
    SMA接头老化了只能报废?分享几个<b class='flag-5'>你</b><b class='flag-5'>不知道</b>的维护回春术

    【文章转载】不知道的Vector | VectorCAST/C++的AI辅助测试功能

    从2026版本开始,VectorCAST/C++推出首批AI辅助测试功能,旨在帮助开发团队解决单元测试过程中的两个核心难点:1.如何根据需求描述自动生成可验证逻辑功能的单元测试用例(Req2test):基于自然语言需求输入,利用大语言模型自动生成符合测试框架规范的测试用例代码,减少手工编写用例的工作量。2.如何基于现有代码自动生成单元级别的需求规格说明(Co
    的头像 发表于 04-29 10:03 259次阅读
    【文章转载】<b class='flag-5'>你</b><b class='flag-5'>不知道</b>的Vector | VectorCAST/C++的AI辅助测试功能

    SMB接头老化了只能扔掉?分享几个不知道的日常维护「回春术」

    SMB接头老化失效只能换新?其实很多时候只是“假死”。本文由拥有10年一线经验的射频工程师撰写,深度分享SMB接头的日常维护“回春术”。从诊断真假老化、无水乙醇精确清洗,到外导体弹性的物理复位技巧,全方位解析如何延长连接器使用寿命。结合德索连接器(Dosin)的精密加工与铍青铜选材经验,手把手教你降低B端设备运维成本,解决信号跳变顽疾,纯干货分享,建议收藏。
    的头像 发表于 04-27 15:06 86次阅读
    SMB接头老化了只能扔掉?分享几个<b class='flag-5'>你</b><b class='flag-5'>不知道</b>的日常维护「回春术」

    PCB棕化工艺不同,高速信号损耗差老远了!

    开着法拉利加92号汽油能不能跑得快我不知道,但是好的PCB铜箔遇到差的PCB棕化工艺损耗就一定会变大……
    的头像 发表于 04-21 17:29 579次阅读
    PCB棕化工艺不同,高速信号损耗差老远了!

    PicoClaw v0.2.4 发布——迄今最大更新

    2026年3月25日PicoClaw团队发布v0.2.4版本这次更新很大,大到我们自己都觉得,不说清楚可能不知道升级了什么所以这篇文章只说一件事:这次更新对意味着什么一、AI助手会"
    的头像 发表于 03-26 12:04 567次阅读
    PicoClaw v0.2.4 发布——迄今最大更新

    的EMC瓶颈,不是经验不够,是体系不全

    EMC的EMC瓶颈,不是经验不够,是体系不全现在的状态,是不是这样?产品送测前,心里完全没底,不知道哪一项会挂传导发射超标,换了十几种电容电感,还是压不下去静电打几下,系统重启,
    的头像 发表于 03-10 16:41 487次阅读
    <b class='flag-5'>你</b>的EMC瓶颈,不是经验不够,是体系不全

    的导航早已“中国芯”:北斗如何静默取代GPS,成为日常出行主力?

    四大全球卫星系统深度对比+实测验证指南当你说“打开导航”时,知道背后是哪颗“星辰”在为指引方向吗?今天,我们将深入探讨全球卫星定位系统的格局,并揭示一个
    的头像 发表于 02-05 19:33 1091次阅读
    <b class='flag-5'>你</b>的导航早已“中国芯”:北斗如何静默取代GPS,成为日常出行主力?

    组合导航不知道怎么选,看这里

    单一导航难适配复杂作业?MEMS组合导航来帮你,通过融合卫星定位与惯性测量技术,以“1+1>2”优势破解全行业导航难题。 我司组合导航提供三种方案可选,从01到03到05,搭载从导航级到战术级到消费级MEMS陀螺仪与加速度计,通过微机电技术集成在毫米芯片上,搭载卫星模块以及其他器件,尺寸仅有65mm*70mm*45.5mm,以下是三种方案详细介绍: 超高精度方案—ER-GNSS/MINS-01 产品定位 一款拥有测绘级超高精度的组合导航,是市面上采用MEMS技术组合导航系统中
    的头像 发表于 01-08 15:47 390次阅读

    断电时,的后备电源真能启动吗?多数人不知道的UPS保养真相

    断电那一刻,的后备电源真的能顶上吗?深夜,机房警报突然响起,屏幕瞬间熄灭——不是演习,市电真的断了。所有人的心都提到了嗓子眼,三秒后,服务器指示灯重新亮起,UPS电源的显示屏稳定地闪烁着运行
    的头像 发表于 12-05 13:37 2010次阅读
    断电时,<b class='flag-5'>你</b>的后备电源真能启动吗?多数人<b class='flag-5'>不知道</b>的UPS保养真相

    使用MCU200T的板子能不能做RVSTAR上的实验例子?

    手里只有一块MCU200T的板子,看到老师做的RVSTAR上的例子,不知道能不能在MCU200T上面做
    发表于 11-05 12:55

    现在流行来中国看赛博朋克

    中国以外的人不知道中国有多强
    的头像 发表于 10-10 22:43 4660次阅读
    现在流行来中国看赛博朋克

    工业数据连接器的稳定重要性不会不知道吧?

    我曾参与一家汽车零部件工厂的智能化改造项目,本以为升级设备就能大幅提升生产效率,却因一时疏忽选用了稳定性欠佳的工业数据连接器。试运行期间,生产线频繁出现数据传输中断,导致机械臂动作错乱,生产出大量残次品,直接经济损失高达数十万元。这次教训让我深刻明白,在工业领域,数据连接器的稳定容不得半点马虎。 工业数据连接器,作为工业设备间数据交互的核心枢纽,其稳定性决定着整个工业生产系统能否正常运转。不同于普通消
    的头像 发表于 07-03 16:32 683次阅读

    关于OFDM 不知道的那些事?#OFDM #5G技术 #通信技术

    通信技术
    安泰仪器维修
    发布于 :2025年06月24日 18:31:22

    编译错误: error: unrecognizable insn,不知道原因,请教!

    用start_gui.exe生成的代码,再用SEGGER Embedded打开,编译有错,不知道是什么原因,请教各位大佬?
    发表于 06-23 14:45

    瑞萨RA单片机在e2 studio环境下printf编译出错的问题解析

    最近看到有一些网友在讨论关于:瑞萨RA单片机在e2 studio环境下printf编译出错的问题。
    的头像 发表于 05-24 15:51 1762次阅读
    瑞萨RA单片机在e2 studio环境下<b class='flag-5'>printf</b>编译出错的问题解析