深入解析printf/spintf/snprintf中的类型转换
来源:互联网 发布:职业网络打假 编辑:程序博客网 时间:2024/04/30 17:27
本文通过两个问题出发,跟踪汇编代码,阐明了函数 printf/spintf/snprintf中的类型转换的规律。
1 问题的提出
源码1:
#include <stdio.h>f1(){ double x = -5.5625; printf("%d\n",x); //输出为0,为什么?}main(){ f1();}源码2:
#include <stdio.h>f1(){ int y=1; printf("%f\n",y); //输出的值是随机的, 为什么?}main(){ f1();}分析这个问题之前,我们要首先了解浮点数在计算机中是如何存储的。
2 浮点数在计算机中的存储
在c语言中,浮点数有两种数据类型float和double,float占用32bit,double占用64bit。float的存储格式遵从IEEE R32.24,参见图1;而double的存储格式遵从R64.53,参见图2。
1.1 float
float x= -5.5625;
接下来,详细解析x在内存中的存储格式。-5.5625 的二进制表示为-101.1001,用二进制的科学计数法表示为-1.011001*22,
●符号位
由于是负数,所以为1
●指数位
为2。由于指数部分采用8bit存储,取值范围为[-127,128],指数部分采用加127存储,即在指数部分存储的数据为2+127=129,即10000001。
●尾数部分为011001。所以x在内存中存储格式见图3。对应的16进制整数为0xc0b20000。
1.2 double
double y = -5.5625;
-5.5625 的二进制表示为-101.1001,用二进制的科学计数法表示为-1.011001*22,
●符号位
由于是负数,所以为1
●指数位
为2。由于指数部分采用11bit存储,取值范围为[-1023,1024],所以指数部分采用加1023存储,即在指数部分存储的数据为2+1023=1025,即100 0000 0001。
●尾数部分
为011001。
所以y在内存中存储格式见图4。对应16进制整数为0xc01640000000 00 00。
3 分析问题
以下是利用gdb跟踪调试源码1的过程。发现,printf("%d\n",x);根本就没有把x由double类型转换为int类型,只是截取了x的低4个字节,并输出。
(gdb) b main(gdb) rBreakpoint 1, main () at 1.c:99 f1();(gdb) display /i $pccall 0x8048354 <f1>(gdb) sipush %ebp ;保存上层函数的栈的上下文(gdb) simov %esp,%ebp ;保存上层函数的栈的上下文(gdb) sisub $0x28,%esp ;为函数f1分配的栈,大小为28字节(gdb) sidouble x = -5.5625;fldl 0x8048480 ;把0x8048480存储的双精度浮点数置入浮点寄存器%st(0)(gdb) p/x (char[8])*0x8048480$1 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x16, 0xc0} ;证明了内存0x8048480处存储常量-5.5625(gdb) sidouble x = -5.5625;fstpl 0xfffffff8(%ebp) ;把浮点寄存器%st(0)的值置入内存(%ebp-8)处(gdb) info all-registersst0 -5.5625 (raw 0xc001b200000000000000) ;证明了%st(0)存储的浮点数为-5.5625(gdb) siprintf("%x\n",x);fldl 0xfffffff8(%ebp) ;把内存(%ebp-8)处的双精度浮点数置入%st(0),即-5.526(gdb) printf("%x\n",x);fstpl 0x4(%esp) ;把%st(0)中的值置入内存(%esp+4),即把printf的第二参数压栈(gdb) i r espesp 0xbfb00320 0xbfb00320(gdb) p/x (char[8])*0xbfb00324$2 = {0x6c, 0x95, 0x4, 0x8, 0x38, 0x3, 0xb0, 0xbf}(gdb) siprintf("%x\n",x);movl $0x8048478,(%esp) ;把函数printf的第一个参数压入栈中,用栈来传递参数(gdb) p/x (char[8])*0xbfb00324;显示printf的第二个参数的值。printf的格式串中”%d”在指明第二参数是int类型,即使实际传递的;是double类型,也没有进行类型转换,即没有把x由double类型转换为int类型,printf在取值是;直接读取前4个字节00 00 00 00,所以printf输出为0$3 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x16, 0xc0} ; (gdb) siprintf("%x\n",x);call 0x8048298 <printf@plt> ;调用printf函数(gdb) p/x (char[8])*0xbfb00324$4 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x16, 0xc0}
由此引申开来发现如下规律:
● %d/%x/%u --> float/double
利用%d/%x/%u输出float/double类型变量时,会得到意想不到的结果,因为不会进行类型转换,而是把变量截断为4个字节并输出。原因在前面已经给出。
● %f --> int
利用%f输出int变量,输出的值是随机的。
f1(){ int x = 1; printf("%f\n",x); ;输出的值是随机的}
对应的汇编代码:
movl $1, -4(%ebp)movl -4(%ebp), %eaxmovl %eax, 4(%esp) ;没有把x转换为float类型movl $.LC0, (%esp);printf会读取内存4(%esp)除的8个字节,由于后4个字节的值是随机的,所以输出的值是随机的call printf
●%d/%x/%u --> char/short
利用%d/%x/%u输出char/short类型变量时,会对char/short类型进行符号位扩展,扩展为4个字节。
f1(){ char x = 0x80; printf("%x\n",x);}
对应的汇编代码:
movb $1, -1(%ebp)movsbl -1(%ebp),%eax ;把x符号扩展为4个字节movl %eax, 4(%esp)movl $.LC0, (%esp)call printf
4 结论
由于float/double存储机制迥异于char/short/int/long long的存储机制,所以利用%f来输出char/short/int/long long变量,或者是利用%d输出float/double变量,得到的结果会令你大吃一惊。所以我们在使用printf/sprintf/snprintf函数时,应该严格的按照参数的类型指定格式串。
5参考
[1] http://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html
- 深入解析printf/spintf/snprintf中的类型转换
- 深入解析printf/spintf/snprintf中的类型转换
- printf、sprintf、snprintf深度用法解析
- spintf
- 深入理解Javascript中的自动类型转换
- 深入解析C++中的引用类型
- snprintf与printf
- printf、sprintf、snprintf简述
- printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - 输出格式转换
- printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - 输出格式转换
- printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - 输出格式转换
- printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - 输出格式转换
- printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - 输出格式转换函数
- snprintf和printf的区别
- printf, fprintf, sprintf,snprintf 区别
- 深入源码解析Python中的对象与类型
- 全面解析js中的数据类型与类型转换
- snprintf用法解析
- 算是阅读笔记吧----一些数据库的设…
- RabbitMQ 消息队列服务器 进程通讯
- RabbitMQ的使用
- javascript 转换Json字符串为JSON对象
- Linux下的虚拟摄像头
- 深入解析printf/spintf/snprintf中的类型转换
- UVaOJ 10055 - Hashmat the Brave Warrior
- Ubuntu 中软件的安装、卸载以及查看的方法总结
- 搭建Android开发环境
- CGContext小记
- 如何复制GSM SIM卡
- Qt点滴
- iOS6 App Store 更新:扑克式App搜索结果布局 马太效应加重!
- 各大解析漏洞总结