C++反汇编学习笔记(一)
Chinese:
C++整数与浮点数的存储方式
1、整数类型
无符号整数的所有位都用来表示数值,有符号整数的最高位是符号位,其余位以补码格式存储。补码规则就是用0减去这个数的绝对值(取反加一)。首先我们知道x+x(反码)=0xFFFFFFFF, 因此x+x(反码)+1=0, 因此x(补码)=0-x=x(反码)+1。补码的好处就是方便计算机做加法。
2、浮点数类型
C++中的浮点数类型分为float和double,float占4个字节、double占8个字节。浮点数的操作不会用到通用寄存器,而是使用浮点协处理器的浮点寄存器。
浮点数的编码方式采用的是IEEE规定的编码标准,float和double两种类型的数据的转换原理相同,不同的只是表示范围。IEEE规定的浮点数编码会将一个浮点数转换成二进制数。以科学计数法划分,将浮点数拆分为3个部分:符号、指数、尾数。
float占4个字节,最高位是符号位,紧接着的8位表示指数,其余位表示尾数。
实例分析:
(1) 12.25f 转换成二进制是1100.01,1100.01 = 1.10001 * 2 ^ 3。由于在二进制中,尾数的最高位始终是1,所以忽略不计。因此尾数位是1000100000...剩下添0。指数部分有一个规定,由于指数可能出现负数,十进制的127表示为二进制为01111111。IEEE编码方式规定,当指数域小于01111111时为一个负数,反之为正数。因此01111111为0,所以指数位为3+127=130,二进制为10000010。
符号位:0;指数位:10000010;尾数位:10000100000000000000000;表示成16进制为0x41440000
(2) -0.125f转换成二进制是-0.001,由于是负数所以符号位是1, 0.001=1.0 * 2 ^ -3,尾数最高位始终为1,所以忽略不计。尾数位是00000....,指数位是-3+127=124,二进制是1111100。
符号位:1;指数位:01111100;尾数位:00000000000000000000000;表示成16进制为0xBE000000
(3)1.3f转换成二进制的时候,由于0.3转换成二进制会得到一个无穷值: 0.3=0.25+0.03175+....,所以尾数位会舍弃多余的部分。转换成二进制就约等于1.01001100110011001100110,到第23位终止。指数位为0+127,二进制为01111111
符号位:0;指数位:01111111;尾数位:01001100110011001100110;表示成16进制为0x3FA66666
所以我们得出IEEE编码转换后,得到的是一个近似值,存在一定的误差。所以这也就解释了为何在c++判断浮点数值是否为0时,要做一个区间比较而不是直接进行等值比较。
float fNum; float fScope = 0.0001f; if (fNum >= -fScope && fNum <= fScope) { // fNum等于0 }
3、double类型的IEEE编码
double类型和float类型大同小异,只是double类型表示的范围更大,精度更高。最高位是符号位,指数位占11位,尾数位占42位。double扩大了精度,因此指数位需要加上1023,这些都可以推断出来,这里便不再赘述。
4、浮点数指令
浮点数的操作指令和普通数据类型不同,浮点数操作是通过浮点寄存器实现的,而普通数据类型是通过通用寄存器,它们使用的是两套不同的指令。
浮点寄存器是通过栈结构来实现的,由ST(0)~ST(7)一共8个栈空间组成,每个浮点寄存器占8个字节。每次使用浮点寄存器都是率先压入ST(0),不能越过ST(0)直接使用ST(1),当8个寄存器都有数据时,此时再压入数据的时候,ST(7)的值会被丢弃。
整型数据与浮点型数据之间的相互转换,来看一下反汇编出来的指令。
// C++ Code: float fNum = (float)argc; ;将地址ebp+8处的整型数据转换成浮点型,并放入ST(0)中,对应变量argc fild dword ptr [ebp+8] ;从ST(0)中取出数据以浮点编码方式存入地址ebp-4中,对应变量fNum fst dword ptr [ebp-4] // C++ Code: printf("%f", fNum);
;这里对esp执行减8操作是由于浮点数作为变参函数的参数时需要转换为双精度浮点值 ;这步操作是提前准备8字节的栈空间,以便存放double数据 sub esp,8 ;将ST(0)中的数据传入esp中,并弹出ST(0) fstp qword ptr [esp] ;以下为printf函数调用,略 push offset string "%f" (0042302c) call printf (0040e940) add esp, 0ch // C++ Code: argc = (int)fNum;
;将地址ebp-4处的数据以浮点型压入ST(0)中 fld dword ptr [ebp-4] ;调用函数__ftol进行浮点数转换 call __ftol (0040e688) ;转换后结果放入eax,并传递到ebp+8地址处 mov dword ptr[ [ebp+8], eax // C++ Code: printf("%d", argc); ;略。。。
浮点数虽然占4个字节,但都是以8个字节的方式进行处理。当浮点数作为参数时,不能直接压栈。push指令只能传入4字节的数据到栈中,这样会丢失4字节的数据。所以使用printf以整数方式输出浮点数时会产生错误。
浮点数作为返回值的汇编指令:
// C++ Code: fNum = GetFloat(); ;调用函数GetFloat call @ILT+5 (GetFloat) (0040100a) ;由于浮点数需要特殊处理,浮点数占8字节,无法使用eax进行传递 ;使用浮点寄存器ST(0)作为返回值 fst dword ptr [ebp-4] // C++ Code: float GetFloat() { return 12.25f; } ;将浮点数保存在ST(0)中,在返回值为浮点数的情况下,无法使用eax ;使用ST(0)作为返回值进行传递 fld dword ptr [__real@4@4002c400000000000000 (0042301c)] ret