C++反汇编与逆向分析技术揭秘(第2版)
上QQ阅读APP看书,第一时间看更新

第2章
基本数据类型的表现形式

2.1 整数类型

C++提供的整数数据类型有:int、long、short和long long。在Visual Studio 2019中,int类型与long类型都占4字节,short类型占2字节,long long类型占8字节。

由于二进制数不方便显示和阅读,因此内存中的数据采用十六进制数表示。1字节由2个十六进制数组成,在进制转换中,1个十六进制数可用4个二进制数表示,每个二进制数表示1位,因此1字节在内存中占8位。

在C++中,整数类型又可以分为有符号型和无符号型两种。有符号整数可用来表示负数与正数,而无符号整数则只能表示正数。它们有什么区别?在内存中又如何表示?本章我们一起揭开这些谜题。

2.1.1 无符号整数

在内存中,无符号整数的所有位都用来表示数值。以无符号整型数unsigned int为例,此类型的变量在内存中占4字节,由8个十六进制数组成,取值范围为0x00000000~0xFFFFFFFF,如果转换为十进制数,则表示范围为0~4294967295。

当无符号整型不足32位时,用0来填充剩余高位,直到占满4字节内存空间为止。例如,数字5对应的二进制数为101,只占了3位,按4字节大小保存,剩余29个高位将用0填充,填充后结果为:00000000000000000000000000000101;转换成十六进制数0x00000005之后,在内存中以“小尾方式”存放。“小尾方式”存放以字节为单位,按照数据类型长度,低数据位放在内存的低端,高数据位放在内存的高端,如0x12345678,会存储为78 56 34 12。相应地,在其他计算机体系中,也有“大尾方式”,其数据存储方式和“小尾方式”相反,高数据位放在内存的低端,低数据位放在内存的高端,如0x12345678,会存储为12 34 56 78。如果大家对此仍有疑问,可以查阅2.7节。

无符号整数不存在正负之分,都是正数,故无符号整数在内存中都是以真值的形式存放的,每一位都可以参与数据表达。无符号整数可表示的正数范围是补码的1倍。

2.1.2 有符号整数

有符号整数中用来表示符号的是最高位,即符号位。最高位为0表示正数,最高位为1表示负数。有符号整数int在内存中同样占4字节,但由于最高位为符号位,不能用来表示数值,因此有符号整数的取值范围要比无符号整数取值范围少1位,即0x80000000~0x7FFFFFFF,如果转换为十进制数,则表示范围为-2 147 483 648~2 147 483 647。

在有符号整数中,正数的表示区间为0x00000000~0x7FFFFFFF;负数的表示区间为0x80000000~0xFFFFFFFF。

负数在内存中都是以补码形式存放的,补码的规则是用0减去这个数的绝对值,也可以简单地表达为对这个数值取反加1。例如,对于-3,可以表达为0-3,而0xFFFFFFFD+3等于0(进位丢失),所以-3的补码就是0xFFFFFFFD了。相应地,0xFFFFFFFD作为一个补码,最高位为1,视为负数,转换回真值同样也可以用0-0xFFFFFFFD的方式表示,于是得到-3。为了计算方便,人们也常用取反加一的方式求补码,因为对于任何4字节的数值x,都有x+x(反)=0xFFFFFFFF,于是x+x(反)+1=0,接下来就可以推导出0-x=x(反)+1了。

在我们讨论的C/C++中,有符号整数都是以补码形式存储的,而且在几乎所有的编程语言中都是如此,这是为什么呢?因为计算机只会做加法,所以需要把减法转换为加法。

设有符号数xy,求x-y的值,我们可以推导出x-y=x+(0-|y|),根据补码的规则,当y为负数的时候,0-|y|等价于y的补码。对于y的补码,我们记为y(补),所以x-y=x+y(补)。

例如,(3-2)可转换成(3+(-2)),运算过程为3的十六进制原码0x00000003加上-2的十六进制补码0xFFFFFFFE,从而得到0x100000001。由于存储范围为4字节大小,两数相加后产生了进位,超出了存储范围,超出的1将被舍弃。进位被舍弃后,结果为0x00000001。

值得一提的是,对于4字节补码,0x80000000所表达的意义可以是负数0,也可以是0x80000001减去1。因为0的正负值是相等的,没有必要再用负数0,所以就把这个值的意义规定为0x80000001减去1,这样0x80000000也就成为4字节负数的最小值了。这也是有符号整数的取值范围中,负数区间总是比正数区间多一个最小值的原因。

在数据分析中,如果将内存解释为有符号整数,则查看用十六进制数表示时的最高位,最高位小于8则为正数,大于8则为负数。如果是负数,则须转换成真值,从而得到对应的负数数值,如图2-1所示。

图2-1 有符号负数的内存信息

在图2-1中,地址0x010FFCC4对应的4字节为变量var的数据信息。var为一个有符号整数,在内存中的信息为0xFFFFFFFF,最高位为1,说明变量var为一个负数。按照转换规则,内存中存放的十六进制数为一个补码,须转换成真值再进行解释。0减去0xFFFFFFFF,或者对0xFFFFFFFF取反加1,都可以得到真值-1。

那么,如何判断一段数据是有符号类型还是无符号类型呢?这就需要查看指令或者已知的函数操作内存地址的方式,根据操作方式或函数相关定义得出该地址的数据类型。如API调用MessageBoxA,它有4个参数,查看帮助手册得知,第4个参数为一个无符号整数,从而可分析出这个传入数值的类型。

有符号整数在算术运算中有许多特殊之处,更多有关有符号整数的操作及识别过程的内容请参考第4章。