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

    文章

    67

    浏览量

    19215
  • 程序
    +关注

    关注

    117

    文章

    3836

    浏览量

    84745
  • Printf
    +关注

    关注

    0

    文章

    84

    浏览量

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

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

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

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

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

    不知道怎么画原理图了

    时,这样的问题,也有这么多?!1、电阻的表示方法是第一种,还是第二种?中间是方框还是折线?方框做多大?现场一片混乱立马分成N派。普通的电阻都这样,这么多种电阻现
    的头像 发表于 04-30 18:40 799次阅读
    <b class='flag-5'>不知道</b>怎么画原理图了

    PCB设计仿真,“缝合电容”我怎么可能不知道

    。 案例1: 相信很多人都遇到走线跨分割地平面的情况,例如下面的模型所展示的: 大家都知道跨分割肯定对信号有影响了,那你们能想到的优化方案是什么呢!什么,告诉我不跨分割平面不就解决了吗!要是能不
    发表于 04-28 15:44

    PCB设计仿真,“缝合电容”我怎么可能不知道

    说到“缝合电容”,虽然我已经听你们说过800多遍了,但是还是忍不住问一个很简单的问题:额,它到底是啥。。。
    的头像 发表于 04-28 15:43 458次阅读
    PCB设计仿真,“缝合电容”我怎么<b class='flag-5'>可能不知道</b>

    球压试验装置:可能不知道的电气安全卫士

    球压试验装置,简单来说,是一种用于评估材料在高温和压力共同作用下抗形变能力的专业设备 。其核心测试原理基于一个看似简单却极为精妙的设计:将一个规定直径(通常为 5mm)的钢球,在特定压力(一般为 20N±0.2N )下,压在被测试材料表面,并将整个装置置于设定高温的烘箱中保持一段时间(常见为 60 分钟) 。测试结束后,通过测量材料表面留下的压痕直径,来判断
    的头像 发表于 04-24 13:33 850次阅读
    球压试验装置:<b class='flag-5'>你</b><b class='flag-5'>可能不知道</b>的电气安全卫士

    磁芯参数的介绍(可下载)

    磁芯在开关电源里面应用非常的多,但是我们对磁芯里面的一些参数了解的非常的少很多的初学者在应用磁芯的时候,都是去套公式,但对于一些公式里面的参数代表什么意思根本不知道甚至有工作几年的工程师可能不知道
    发表于 03-14 14:11 2次下载

    DLP3010在设置时不知道如图所示的input和output指什么,这样的img文件从哪得到?

    目的是更改开机图片,在设置时不知道如图所示的input和output指什么,这样的img文件从哪得到? 以下是各页设置情况,GUI使用的是3.1.0.3,EVM是2.2.0.6
    发表于 02-25 07:23

    关于TI集成电路命名标识那些不知道的事儿

    集成电路
    芯广场
    发布于 :2025年01月21日 17:47:40

    关于射频导纳物位开关不知道的知识点

    它作为一种重要的工业自动化设备,在生产领域中扮演者重要的角色,这款物位仪表就是射频导纳物位开关,它可以用于测量和控制物料的位置和数量。它之所以受欢迎是因为产品的防刮料功能好、测量数值准确、适用的范围广,能够帮助工业生产领域的人们有效的解决一些传统物位开关的限制问题。 这款物位开关的稳定性高、测量数值准、适用场合多等优点,且他还能配备报警指示灯等功能,能够让用户更为直观的获取到它测量出来的数值信息,方便
    的头像 发表于 01-21 13:56 784次阅读
    <b class='flag-5'>关于</b>射频导纳物位开关<b class='flag-5'>你</b><b class='flag-5'>不知道</b>的知识点

    ADS805E测量跳跃幅度很大,不知道是为什么?

    我按照手册上直流耦合连接电路,输入是1.5V~3.5V范围,SEL与VREF相连,用430定时器输出转换时钟,结果测量直流时,AD测量结果在几个值之间跳跃,跳跃幅度很大,不知道是为什么,引脚电压控制用3.3V。
    发表于 01-21 07:46

    测的值不定然后开始减小直到为0,不知道怎么回事?

    ADS1247对寄存器可读可写 可是配置完02这个地址的时(写0x30)在vrefout测不到2.048v,测的值不定然后开始减小直到为0,不知道怎么回事?
    发表于 01-21 06:27

    想用AMC1203做电流采样,用AMC1210做SINC滤波,不知道这样可不可行?

    想用AMC1203做电流采样,用AMC1210做SINC滤波,不知道这样可不可行? 网上有帖子说AMC1210有问题,你们不推荐采用AMC1210做SINC滤波,而是推荐采用CPLD做SINC滤波,不知道是不是真的?
    发表于 01-14 07:22

    采集4个通道,ADS131E08能不能达到24位64KSPS?

    我在论坛上看到ti的工程师回复说, 帖子说当ADS131E08达到24位,采样率只能达到16KSPS,我不知道他们是不是8个通道的AD都用上了,我这需要采集4个通道,不知道能不能达到24位
    发表于 12-26 07:10

    有关UV固化不知道的技术细节

    UV固化技术中,光引发剂吸收UV光产生活性自由基触发聚合反应,包括链引发、增长、终止和交联。反应速率受UV光强、光引发剂浓度、单体类型等因素影响,UV固化技术具有快速固化特性。
    的头像 发表于 12-06 09:33 1445次阅读