问题: 计算机存储中,整型数据是以补码的形式存储在存储介质中,而浮点型数据是遵循IEEE754标准将数据存储在存储介质中。当整型变量赋值给浮点型变量的时候,对于两种不同的存储形式,计算机是怎么完成数据的复制?
首先,编一个简单的C程序,通过编译生成的汇编文件看一下计算机是怎么处理的。
/*文件名:1.c*/
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 9;
float b = 1.0;
b = a;
return 0;
}
生成汇编代码:
gcc -S 1.c -o 1.s
汇编代码:
.file "1.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl $9, -4(%ebp) ;初始化a = 9
movl .LC0, %eax
movl %eax, -8(%ebp) ;初始化b = 1.0
fildl -4(%ebp) ;fildl指令用于将整型压入浮点型寄存器FPU的数据寄存器st
fstps -8(%ebp) ;将st寄存器准换为单精度数据保存到b变量中,出栈
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .rodata
.align 4
.LC0:
.long 1065353216 ;这是十进制,表示浮点数1.0,这里根据CPU的情况,将浮点型常量以长整型8个字节进行存储
.ident "GCC: (GNU) 4.8.5"
.section .note.GNU-stack,"",@progbits
这里需要注意两条指令:
1、fildl -4(%ebp)
;fildl指令用于将整型压入浮点型寄存器FPU,就是将a存储单元内的数据(补码格式)经过硬件转换为浮点型存储格式存储到st数据寄存器中,即从内存中到寄存器中。
FPU的数据寄存器共有8个,每个都是80个位(10字节),如果是浮点型数据复制到80个位寄存器中,不是简单的从最低位开始,逐一进行对应复制,需要根据符号位、阶数位、尾数位进行对应的转换复制。
2、fstps -8(%ebp)
将st数据寄存器中的浮点数保存到b变量中,即从寄存器到内存中,因为st数据寄存器是80位的,一般复制到单精度、双精度会造成数据的数据丢失,导致程序出现奇怪的问题,所以为了稳妥起见,尽量使用变量类型为long double而非float、double型。
参考资料:
剖析Intel IA32架构下C语言及CPU浮点数机制
通用寄存器 |
顾名思义,通用寄存器是那些你可以根据自己的意愿使用的寄存器,但有些也有特殊作用,IA32处理器包括8个通用寄存器,分为3组。
1、数据寄存器:
EAX 累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据。
EBX 基址寄存器,常用于地址索引
2、变址寄存器:
ECX 计数寄存器,常用于计数;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器.
EDX 数据寄存器,常用于数据传递。
3、指针寄存器:
EBP为基址指针(Base Pointer)寄存器,存储当前栈帧的底部地址。
ESP为堆栈指针(Stack Pointer)寄存器,一直记录栈顶位置,不可直接访问,push时ESP减小,pop时增大。
指令指针寄存器 |
EIP 保存了下一条要执行的指令的地址, 每执行完一条指令EIP都会增加当前指令长度的位移,指向下一条指令。用户不可直接修改EIP的值,但jmp、call和ret等指令也会改变EIP的值,jmp将EIP修改为目的指令地址,call修改EIP为被调函数第一条指令地址,ret从栈中取出(pop)返回地址存入EIP。
寄存器详细参考:
汇编速查-IA32寄存器组织
拓展阅读: