c语⾔格式输出剖析——⽤%d输出float类型数据与int类型%f格
式输出
C语⾔学习实践
摘要
本⽂将从C语⾔变量的本质,不同类型变量在内存中的存储⽅式,类型强制转换,格式输出4个⽅⾯阐述C语⾔初学阶段的⼀些问题。
关键词:内存存储,类型强制转换,反汇编
1. 变量
变量来源于数学,是计算机语⾔中能储存计算结果或能表⽰值抽象概念。在诸如C语⾔等⾼级语⾔中,变量的使⽤屏蔽了数据的底层细节,使得⾼级语⾔程序员不必像汇编程序员那样关⼼数据与硬件之间的关系。为了探究C语⾔中变量在内存中的存储形式,可以借助反汇编查看汇编语⾔以及内存数据。
2. 变量在内存中的存储形式
在内存中,⽆论哪种数据类型的数据,都是以相应长度的⼆进制码存取。从内存取数据是,如果不按照定义数据类型的⽅式取数据,所取数据就会错误。
2.1  ⽤反汇编查看变量内存数据
(1)  实验代码如下。在赋值部分打点后调试,转⼊反汇编。
(2)  在监视窗⼝查看变量的内存地址,并在内存窗⼝中查看数据。
浮点型变量float
整形变量_4ByteData的数据在以内存地址0x0023FA58起始的四个Byte中存放:【注意】 Intel处理器是⼩端机,数据⾼位在⾼地址,地位在地址。所存数据:4Bytes的⼗六进制数 0x12345678
单精度浮点型变量fl的数据在内存中的存储:
双精度浮点型变量df的数据在内存中的存储:
字符型变量ch的数据在内存中存储;
结论:
(1)  局部变量存储在函数栈中,且该栈向低地址⽣长,所以先定义的局部变
量在较⾼内存地址(⽐如_4ByteData在0x0023FA58,ch在0x0023FA33),并且局部变量之间并⾮紧密排布,⽽是由8个Byte 的cc数据隔开。变量周围塞些CCCCCCCC,这可能是编译器提供的⼀种保护机制,越界了好出断⾔。通过⽹上查资料,这是VC在Debug时给变量留出空间,⽤来检查stack overflow。
⽤release调试,就不会有多余的cc。(但是注意这⾥char型变量与之前的局部变量之间仍有数据,我猜测这⾥是应为有变量对齐的缘故,char型变量也占了4byte,只不过多余的3byte由其他数据填充。)
(2)  通过观察不同类型的变量在内存中的存储情况,可以发现:
在32位机器,VS2010 IDE中,⼀个int型数据占4B,float型变量占4B,double型变量占8B,char型占1B。
2.2    IEEE754 单精度数的格式
单精度浮点数占据4个字节,4个字节的分配如下:
(a)第⼀位为符号位,0表⽰正,1表⽰负;
(b)第2~9位为阶码,采⽤移码表⽰;
(c)第10~32位为尾数,采⽤原码表⽰。
给定32位串,如何转换成⼗进制数:
假设内存中存在32位串:00 00 00 3f,因为INTELCPU采⽤little endian存储⽅式,所以其真实的值为:
0x 3f 00 00 00。将其写成⼆进制形式:
(1)第⼀步,化为⼆进制
0 01111110 0000000 00000000 00000000
(2)第⼆步
该浮点数为正数,阶码 01111110,移码表⽰(126-127) = -1
尾数 0000000 00000000 00000000
因为在IEEE754中,单精度浮点数有规格化处理,所以其真正尾数部分为
1.0000000 00000000 00000000,其中‘.’为⼩数点
(3)  第三步
根据公式写出实际数值⼤⼩
0.10000000 00000000 0000000 化为⼆进制:0.5
2.3    IEEE754 双精度数的格式
长实数也称双精度数符号位1位,阶码11位,尾数52位
给定32位串,如何转换成⼗进制数:
假设内存中存在64位串:00 00 00 0000 00 e0 3f,因为INTEL CPU采⽤littleendian存储⽅式,所以其真实的值为:      0x 3f e0 00 00 00 00 00 00。将其写成⼆进制形式:
(1)第⼀步
0 01111111110 0000 00000000 00000000 00000000 00000000 0000000000000000
(2)第⼆步
该浮点数为正数,阶码 01111111110,移码表⽰(1022-1023) = -1 尾数 0
因为在IEEE754中,单精度浮点数有规格化处理,所以其真正尾数部分为
1.0,其中‘.’为⼩数点
(3) 第三步
根据公式写出实际数值⼤⼩
0.10 化为⼆进制:0.5
3. 格式化输出
3.1 printf函数调⽤的⼀般形式
printf函数是⼀个标准库函数,它的函数原型在头⽂件“stdio.h”中。但作为⼀个特例,不要求在使⽤ printf 函数之前必须包含stdio.h⽂件。printf函数调⽤的⼀般形式为:printf(“格式控制字符串”, 输出表列)。
其中格式控制字符串⽤于指定输出格式。格式控制串可由格式字符串和⾮格式字符串两种组成。格式字符串是以%开头的字符串,在%后⾯跟有各种格式字符,以说明输出数据的类型、形式、长度、⼩数位数等。如:
“%d”表⽰按⼗进制整型输出;
“%ld”表⽰按⼗进制长整型输出;
“%c”表⽰按字符型输出等
⾮格式字符串原样输出,在显⽰中起提⽰作⽤。输出表列中给出了各个输出项,要求格式字符串和各输出项在数量和类型上应该⼀⼀对应。
3.2 类型不对应下的格式输出
_4ByteData = 0x12345678;  //Hexadecimal:0x12345678 对应Decimal:305419896
fl = 0.5;
df = 0.5;
ch = 65;
printf("%d\n",_4ByteData);
printf("%c\n",_4ByteData);
printf("%d\n",ch);
printf("%f\n",&_4ByteData);
printf("%d\n",fl);
printf("%d\n",df);
分析:
可以看到同样是int型变量,printf("%d\n",_4ByteData);与
printf("%c\n",_4ByteData);其结果分别为305419896(0x12345678对应的⼗进制数),⽽后者只取了4字节数据的最低⼀个字节0x78,所以打印出了AISII码0x78对应的字符’x’。
联系C语⾔中指针的⽤法,我做出假设:格式输出函数printf()根据类型字符%以及变量名,就可以根据数据⾸地址+读取长度的⽅式输出数据。
进⼀步发现:
但是对字符型变量ch使⽤类型字符%d输出,得到的是其ASCII码的⼗进制数,如果按照上述假设,会输出以ch地址起始的4B的数据(这将是⼀个错误数据)。
但实验结果是正确输出了ch字符的ACSII码的⼗进制数。
再对浮点数做实验:
⽤类型字符%d输出单精度数,双精度数,结果均为0。
由上⽂2.1可知单精度浮点变量fl(⼗进制0.5)在内存中占4B,机器码是
0x00 00 00 3f(⼩端机)。现对整数_4ByteData赋值0x3f000000,并使⽤类型字符%f对该整数输出,查看结果:
结果仍是0。这说明即使内存中数据存储的内容⼀样,但是使⽤类型字符%f对整型变量输出,其结果仍然不是浮点数!
综上,原假设值得怀疑!
4. printf()函数类型不对应下的格式输出进⼀步研究
4.1⽤%d输出float类型数据
float fl=0.5;如果⽤printf("%d",fl);输出的是0。 但float型⽤%d输出是否⼀定是0呢,答案肯定不都是0(如下图)。
为什么 0.5 ⽤%d输出的是0?
分析如下:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。