2.1.2 有符号整数
有符号整数中用来表示符号的是最高位—符号位。最高位为0表示正数,最高位为1表示负数。有符号整数在内存中同样占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++中,有符号整数都是以补码形式存储的,而且在几乎所有的编程语言中都是如此,这是为什么呢?因为计算机只会做加法,所以需要把减法转换为加法。
如设有符号数x, y,求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中,地址0x0012FF7C对应的4字节为变量nVar的数据信息。nVar为一个有符号整数,在内存中的信息为0xFFFFFFFF,最高位为1,说明变量nVar为一个负数。按照转换规则,内存中存放的十六进制数为一个补码,需转换成真值再进行解释。0减去0xFFFFFFFF后,或者对0xFFFFFFFF取反加1,都可以得到真值-1。
那么,如何判断一段数据是有符号类型还是无符号类型呢?这就需要查看指令或者已知的函数如何操作此内存地址,根据操作方式或函数相关定义得出该地址的数据类型。如API调用MessageBoxA,它有4个参数,查看帮助得知,第4个参数为一个无符号整数,从而可分析出这个传入数值的类型。
有符号整数在算术运算中有许多特殊之处,更多有关有符号整数的操作及识别过程的内容请参考第4章。