在了解了变量的使用方式之后,本节来讲讲数在反汇编代码中的简单运算。
计算机在使用数的时候一般会用到二进制,十进制和十六进制。
二进制是计算机技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。现在的CPU使用的基本都是二进程数,用高电平表示1,低电平表示0。为了方便区别,一般在二进程数后追加一个字母B,例如二进制数10则表示为10B。B是二进制的英文binary的首字母。
在人们的日常生活使用的数一般都是十进制数。人类的各种古文明都很有默契地使用十进制数,这与人类有十根手指有密切的关系。十进制数使用十个数码0123456789来表示,逢十进一。为了方便区别,一般在十进程数后追加一个字母D,例如十进制数10则表示为10D。D是十进制的英文decimal的首字母。
在计算机中使用的十进制数一般是BCD码,BCD码用4位二进制数来表示1位十进制数。例如在二进制数10000B表示十进制数的16D,而BCD码10000却表示十进制数10D,它们在内存中的储存形式完全相同,但表示的数却不同。
虽然计算机在处理数据时使用的是二进制数,但是二进制数表示起来是很不方便的,例如一个十进制数1000000D要表示成二进制数则为11110100001001000000B。4位二进制数能表示的最小的数为0000B,能表示的最大的数是1111B,即十进制数15D,这样就可以用4位二进制数来表示16个数字,把这十六个数字分别用一个数码来表示,于是就行成了十六进制数。十六进制数用01234567890ABCDEF这十六个数码来表示0-15D这十六个数,每逢十六进一。为了方便表示,为了方便区别,一般在十六进程数后追加一个字母H,例如十六进制数10则表示为10H。H是十六进制的英文hexadecimal的首字母。一般在程序中用到的十六进制数的表示方法是在数之前加前缀0x,例如十六进制数10则表示为0x10。
通常这三种进制的数之间还需要互相转化。一位十六进程数和四位二进制数是互相对应的,对应关系如下:
十六进制数 二进制数 十六进制数 二进制数
0 0 8 1000
1 1 9 1001
2 10 A 1010
3 11 B 1011
4 100 C 1100
5 101 D 1101
6 110 E 1110
7 111 F 1111
了解了这个转化方式那要转化就很容易了,例如十六进制数0xBC就可以用二进制数10111100B;相反地,二进制数1001010B也可以表示为十六进制数0x4A。二进程数与十进制数之间的转换也是比较容易的。我记得当年学《微型计算机原理》的时候课本上有一句口诀:十进制数转化二进制数,除二取余;二进制数转化十进制数,乘二取整。听起来可能不太好理解,但这是转化方法的精髓。把十进制数转换成二进制数,是一个连续除2的过程,把要转换的十进制数数,除以2,得到一组商和余数;再用计算得到的商来计算,除以2,又得到另一组商和余数。这样一直计算到得到的商为零为止,计算结束后把每一次计算的余数从后往前连起来就得到了这个十进制数对应的二进程数。例如现在有一个十进制11D,转化过程如下:
11 / 2 商:5 余:1
5 / 2 商:2 余:1
2 / 2 商:1 余:0
1 / 2 商:0 余:1
把每一次计算的余数从后往前连起来则为1011,这样就计算出了十进制数11D对应的二进制数1011B。二进制数转化十进制数相对简单一点。二进制数从右往左数第n位表示2的n-1次幂,当第n位为0时忽略,把第n位为1的所有幂加起来就得到了这个十进制数。例如一个二进制数1011B,转化过程如下:
2^3 + 2^1 + 2^0 = 8 + 2 + 1 = 11
那么11D就是二进制数1011B所对应的十进制数。
再来看看另一个问题,机器码的表示方法。机器码的出现主要是为了解决计算机中数的符号和计算问题。这里介绍计算机的原码、反码、补码。
原码是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位),符号位为0表示正数或者0,符号位为1表示负数,其余位表示数值的大小。例如一个int型的十进制数100,用原码表示则为:0000000001100100;同样一个int型的十进制数-100,用原码表示则为:1000000001100100。用原码这样来表示一个数是很直观的,但是它有一个很严重的缺点,就是源码不可以直接进行运算。1 + (-1) = 0,这是很显然的,用源码计算:
0000000000000001 + 1000000000000001 = 1000000000000010
用原码计算的结果为1000000000000010,转换为十进制数则是-2,这显然是错误的。 反码是原码到补码之间的一个过渡产品,它的求法很简单,正数的反码与其原码相同;负数的反码对其绝对值的原码逐位求反例如一个int 型的十进制数100,用原码表示则为:
0000000001100100
同样一个int型的十进制数-100,其绝对值为100,其绝对值的原码为
0000000001100100,逐位求反则得-100的反码为:1111111110011011。
补码是计算机程序在进行运算时统一使用的机器码,他可以将符号位和其它位统一处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。补码的计算方法也很简单,正数的补码和它的原码相等,负数的补码等于它的反码加一。例如一个int型的十进制数-1,其补码计算过程为:首先求出它的绝对值为1,计算它的绝对值1的原码
0000000000000001,
逐位求反则得-1的反码为:1111111111111110,给它加1就变成了:
1111111111111111。
反码可以直接带入运算,例如用反码来计算1 + (-1):
0000000000000001 + 1111111111111111 = 0
然后来看看几种基本的逻辑运算。逻辑常量只有两个,即0和1,用来表示两个对立的逻辑状态。逻辑运算通常用来测试逻辑常量的真假值。大于、大于等于、小于、小于等于、等于、不等于……这些用来比较的逻辑运算就不讲了,上过小学的读者肯定都会的。这里来看看与运算、或运算、异或运算以及非运算。
与运算,把两个操作数的逐位进行比较,若两个操作数的第n位全为1,则运算结果的第n位为
1,否则为0。汇编语言中用and指令来表示与运算,例如:
0010110011010100
1010110010011010
and
-------------------
0010110010010000
或运算,把两个操作数的逐位进行比较,若两个操作数的第n位若有一个为1,则运算结果的第n位为1,否则为0。汇编语言中用or指令来表示或运算,例如:
0010110011010100
1010110010011010
or
-------------------
1010110011011110
异或运算,把两个操作数的逐位进行比较,若两个操作数的第n位有一个为0,而另一个为1,则运算结果的第n位为1,否则为0。汇编语言中用xor指令来表示异或运算,例如:
0010110011010100
1010110010011010
xor
-------------------
1000000001001110
非运算,只有一个操作数,它的计算方法是把该操作数逐位求反。汇编语言中用not指令来表示非运算,例如:
1010110010011010
not
-------------------
0101001101100101
笔者犹记得,当年教的模拟电路老师的几句简单的口令:与运算,有零则零;或运算,有一则