浮点数:printf(“%d,%d,%f\n“,a,b,c)各输出控制符取值的起始地址

一、问题抛出

先看一个例子:
1、若运行时从键盘上输入9876543210l,则该程序在gcc编译器下的输出结果是()

int main(){
	int a;float b,c;
	scanf("%2d%3f%4f",&a, &b, &c);
	printf("\na=%d,b=%d,c=%f\n",a,b,c);
}

A、a=98,b=765,c=4321.000000
B、a=98,b=0,c=0.000000
C、a=98,b=765.000000,c=4321.000000
D、a=98,b=765.0,c=4321.0

正确答案:B
解析:scanf语句执行完后,a=98,b=765,c=4321,这个语句没有问题。
这是问题的解释:答案解析
原文:
printf函数执行的时候,会先把这三个数字压入栈里,然后再执行打印。压入栈的时候按照数据本身的长度来,首先把c和b压入,并且每一个都是8个字节(printf自动转化为double)。然后再压入a是4个字节。然后再执行打印。打印的时候按照用户指定的格式来出栈。首先打印a,a打印正常。然后又打印4个字节长度的b,在栈里面由于b长度是八个字节,并且b目前是64位的表示方式,数据的后面全是0.(float 变double),电脑是小端存储方式,0存储在距离a近的地方。打印b的时候,打印的4个字节都是0。然后再打印c,c用正常的方式打印,会一下子读取8个字节,正好,读出来的八个字节前面四个字节全是0,自己可以算一下,实在太小了,因此为0。(c低4字节为765,高四字节为765左边的0000)

栈底                                   栈顶 
高字节                                低字节 
4321    0000      765     0000         98 

4字节   4字节    4字节      4字节       4字节 
        打印c              打印b      打印a 

二、printf如何打印的

printf打印时需要解决这几个问题:要打印数据的起始地址是多少、取多少数据、以什么类型解读数据、以什么形式打印。

示例1
#include <stdio.h>
int main(int argc, const char *argv[])
{
	char a = 'a';
	char *p = &a;
	printf("p=%p\n", p);
	printf("p=%#x\n", p);
	printf("&a=%p\n", &a);
	printf("&a=%#x\n", &a);
	return 0;
}

运行结果为:
p=0xbf887f8b
p=0xbf887f8b
&a=0xbf887f8b
&a=0xbf887f8b

函数定义的变量是占用栈空间,printf函数被调用时,首先将形参的内容复制给printf函数中的变量,输出控制符的作用是取多少数据、按什么类型来解读该数据(有无符号、整型或浮点型)、以什么样的形式打印到屏幕(打印多少,如%2.1f)。该例子中,%p的输出形式等于%#x。

示例2
#include <stdio.h>
int main(int argc, const char *argv[])
{
	int a = 0x444FFFFF, b = 0x433FFFFF;
	printf("a=%f\n", a, b);
	return 0;
}
运行结果为:
a=9007196105867263

浮点数转化:
在这里插入图片描述

从该例中说明,printf中%f是从栈中取八个字节进行输出打印,输出控制符%f用于打印单精度和双精度数值,这也说明为什么单精度实参输入会以双精度进行保存。

示例3
/* a.c */
#include <stdio.h>

int main(int argc, const char *argv[])
{	
	char a = 'A';   // 对应的十进制是 65
	char b = 0x11;
	char c = 0x12char d = 0x13printf("a=%d, b=%d\n", a, b, c, d);
	return 0;
}

运行结果为:
a=65, b=17
gcc a.c -o a.out              # 将 a.c 编译成 a.out 可执行目标文件
objdump -d a.out > a.dump     # 生成反汇编文件 a.dump

a.dump:(部分)
在这里插入图片描述

(%d的读取数据为4字节,这两点是难以从上述代码中确定的,从源码上看确实是的,结论就是字符类型是以4字节入栈。)

示例4
#include <stdio.h>
int main(int argc, const char *argv[])
{
	int a = 98;
	float c = 1.0;
	double d = 9.007;
	printf("a=%d, b=%d, c=%x\n", a, d, c);
	return 0;
}
运行结果为:
a=98, b=-2130303779, c=40220395

十进制的-2130303779(负数)转换成十六进制为0x810624DD,同c打印出的0x40220395结合起来,正好就是浮点数d存储在内存中的值。
在这里插入图片描述

在这里插入图片描述
从输出结果可知,以第一个形参变量的起始地址为基址,按照输出控制符进行偏移。例如:
假设a变量内容保存在栈空间中的起始地址为0,再取4字节打印第一个%d;
第二个%d的起始地址:a内容在栈空间的起始地址+%d(4个字节)= 4,再取4字节打印第二个%d。
最后%x的起始地址:a内容在栈空间的起始地址+第一个%d(4个字节)+ 第二个%d(4个字节)= 8,再取4字节打印%x。

小结

1、输出控制符的作用是取多少数据、以什么类型解读数据、以什么形式打印到屏幕(标准输出)。

2、char类型的实参,以4字节为单位入栈;float类型的实参,将扩展到double类型(8字节)的数据,然后以8字节为单位入栈。

3、printf中%f是从栈中取八个字节进行输出打印。

4、printf双引号中的%d、%u、%f等,从输出控制符的基址开始取若干字节进行输出打印,这里输出控制符的基址并不是各形参的起始地址,而是从左到右第一个形参为基址,按输出控制符需要的字节数进行偏移,从而确定后续输出控制符的基址。

三、回到问题

栈中的地址从栈顶到栈底依次为:
a=0x00000062
b=0x4087E80000000000
c=0x40B0E10000000000

打印第一个%d为:0x00000062,即为98。
打印第二个%d为:0x00000000(蓝色),即为0。
打印%f为:0x000000004087E800(这里高4字节部分为c变量的低4字节部分),对应的浮点数越等于2.09E-319,近似于0。

四、参考资料

[1]: printf以"%d"输出浮点数
[2]: printf以%d形式输出浮点数的问题
[3]: 浮点数:不同的内存存储格式,当整型变量赋值给浮点型变量会发生什么?

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值