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

    文章

    61

    浏览量

    18602
  • 程序
    +关注

    关注

    114

    文章

    3631

    浏览量

    79540
  • Printf
    +关注

    关注

    0

    文章

    79

    浏览量

    13478
收藏 人收藏

    评论

    相关推荐

    关于静电放电你不知道的知识

    在整个半导体制造过程中,微粒污染、静电放电损坏以及与此相关联的设备停机,是静电带来的三大问题。
    的头像 发表于 03-27 11:12 171次阅读

    昨天看到消息Altera从Intel独立出来了,不知道大家常用的FPGA是什么?

    昨天看到消息Altera从Intel独立出来了,不知道大家常用的FPGA是什么?我这边分成常规生产治具是altera的,算法和图像相关的使用的是Xilinx的;
    发表于 03-06 13:39

    如何知道嵌入式电子控制单元 (ECU) 中的RAM使用情况?

    知道嵌入式软件构建工具会报告程序闪存使用情况。我认为他们也报告 RAM 使用率,但他们是否报告最大 RAM 使用率? 生成工具可能不知道在运行时将使用多少堆。是否有构建工具不知道的其他 RAM 使用情况? 如何准确找出运行时使
    发表于 01-22 07:02

    直插大功率电感不知道怎么选就看这里

    直插大功率电感不知道怎么选就看这里 编辑:谷景电子 关于直插大功率电感的选型一直是一个难题,要想直插大功率电感的优势在电路中发挥着作用,那么选型这个环节是必不可少并且特别重要。如果你对直插大功率电感
    的头像 发表于 01-04 22:46 136次阅读

    7种MOSFET栅极电路的常见作用,不看不知道

    7种MOSFET栅极电路的常见作用,不看不知道
    的头像 发表于 12-15 09:46 388次阅读
    7种MOSFET栅极电路的常见作用,不看<b class='flag-5'>不知道</b>!

    揭秘pcb是什么物质:你不知道的“化学战士”

    揭秘pcb是什么物质:你不知道的“化学战士”
    的头像 发表于 12-14 10:27 395次阅读

    不知道的FPC,它的发展史竟然是这样的!

    不知道的FPC,它的发展史竟然是这样的!
    的头像 发表于 11-15 10:48 452次阅读

    画PCB可能遇到的问题和解决办法

    不知道你有没有在画PCB呢,在画的时候,遇到了些什么问题呢?
    的头像 发表于 11-13 14:18 1450次阅读
    画PCB<b class='flag-5'>可能</b>遇到的问题和解决办法

    关于伺服电机你可能不知道的20个问题

    发表于 11-06 08:31 0次下载
    <b class='flag-5'>关于</b>伺服电机你<b class='flag-5'>可能不知道</b>的20个问题

    很多插件共模电感厂家都不知道的电感选型小知识

    很多插件共模电感厂家都不知道的电感选型小知识 编辑:谷景电子 共模电感是电路中常用的电子元器件,大部分用于过滤共模干扰信号。在选型的时候,有些误区可能会引起所选不合适的共模电感,从而影响电路的功能
    的头像 发表于 07-13 18:28 392次阅读
    很多插件共模电感厂家都<b class='flag-5'>不知道</b>的电感选型小知识

    请问N76E003如何添加使用printf?

    N76E003 第一次使用,编译完例程,想试试printf,不能用,不知道该如何重定义?
    发表于 06-25 07:01

    关于AnyDesk你不知道的5件事

    游戏玩家会回忆起那些需要将软盘或CD安装到硬盘驱动器上的电脑游戏。不用说,数字时代已经推进了我们今天安装视频游戏的方式,因为从合法游戏平台下载已经取代了旧的CD-ROM。现在,您知道外出时可以使用AnyDesk远程开始下载PC游戏吗?
    的头像 发表于 06-11 15:53 783次阅读

    盘点你不知道的电缆套管知识-科兰

    电缆套管又称保护管、导管,是在电气安装中用于保护电线、电缆布线的管道,允许电线、电缆的穿入与更换。电缆套管是电力工程中推广使用的一种新型套管材料。盘点你不知道的电缆套管知识,希望能够得到帮助。 电缆
    的头像 发表于 05-25 10:24 1129次阅读

    SD卡挂载完成,不知道有没有能够测试SD卡读写速度的例子?

    SD卡挂载完成,不知道有没有能够测试SD卡读写速度的例子?请知道的大侠赐教!!
    发表于 05-12 15:33

    pikascript移植报错不知道是哪里的问题?

    想在fm33lx基础上应用pikaScript做脚本开发,通过env添加了pikaScript的软件包,工程里也出现了对应的文件,但是在编译的时候提示错误,不知道哪里的问题,请大咖指教。
    发表于 05-05 11:49