一、算术及逻辑运算
图3.7列出了一些整数和逻辑运算。大多数操作都以一类指令的形式给出,因为指令可以具有不同操作数大小的不同变体。例如,指令类add包含三条加法指令:addb、addw和addl,分别是字节、字和双字的相加。实际上,上图所示的每个指令类都有对字节、字和双字数据进行操作的指令。操作分为四组:加载有效地址、一元、二进制和移位。二元运算有两个操作数,而一元运算有一个操作数。
1.1 载入有效地址
加载有效地址指令leal实际上是movl指令的一个变体。它从内存读取内容到寄存器,但它并没有读取内存某处存储的数据,而是读取了内存地址本身。在图3.7中,我们使用C地址运算符&S(C语言中使用&来表示读取地址信息)来表示这个计算。这个指令可以用来为以后的内存引用生成指针。此外,它还可以用来简洁地描述常见的算术运算。例如,如果寄存器%edx包含值x,则指令leal 7(%edx, %edx, 4), %eax将寄存器%eax设置为5x+7。注意,这个指令的目标操作数必须是寄存器。
练习:
假设寄存器%eax存储了值x,寄存器%ecx存储了值y。填充下执行如下指令后寄存器%edx的值:
答案:
- 6+x
- x+y
- x+4y
- 7+9x
- 10+4y
- 9+x+2y
1.2 一元及二元运算
第二组中的操作是一元操作,单个操作数同时作为源和目标。此操作数可以是寄存器或内存位置。例如,incl(%esp)指令执行后,堆栈顶部的4字节元素会自增1。
第三组由二进制操作组成,其中第二个操作数用作源操作数和目标操作数。这种语法类似C赋值运算符,例如x+=y。但注意,第一个操作数是源操作数,第二个操作数是目标操作数。例如,指令subl %eax,%edx将寄存器%edx减去%eax中的值。第一个操作数可以是立即数、寄存器或内存位置。第二个可以是寄存器或内存位置。但是,与movl指令一样,两个操作数不能都是内存位置。
练习:
假设如下的内存地址和寄存器存储了下列信息:
在下表中填写指令的目标地址以及运算结果:
Destination | Value |
---|---|
0x100 | 0x100 |
0x104 | 0x98 |
0x10C | 0x110 |
0x108 | 0x14 |
%ecx | 0x0 |
%eax | 0xFD |
1.3 移位运算
最后一组由移位操作组成,首先给出移位量,然后给出要移位的值。我们可以进行算术右移和逻辑右移。移位量被编码为单个字节,因为只有0到31之间的移位量是可能的(我们只需要使用移位量的低五字节)。移位量以立即数形式给出,或在单字节寄存器元素%cl中给出。如图3.7所示,左移位指令有两个名称:sal和shl。两者的效果相同,从右边填充零。右移位指令的不同之处在于sar执行算术移位(用符号位的副本填充),而shr执行逻辑移位(用零填充)。移位操作的目标操作数可以是寄存器或内存位置。我们将图3.7中两种不同的右移操作表示为
>
>
A
>>_A
>>A(算术)和
>
>
L
>>_L
>>L(逻辑)。
练习:
假设我们要为如下的C函数生成汇编代码:
下面给出了程序主题框架,需要我们填充程序的空缺部分(也就是移位部分),注意右移应该按照进行算术右移:
答案:
- sall 2,%eax
- sarl %cl,%eax
1.4 补充:汇编代码和C代码的对应
我们看到,图3.7中所示的大多数指令都可以用于无符号数或有符号补码运算。只有右移需要区分有符号和无符号数据的指令。
下图展示了一个执行算术运算的函数以及对应的汇编代码。函数参数x,y,z存储在据寄存器%ebp偏移8,12,16处:
我们可以看到,汇编代码指令的出现顺序与C源代码不同,同时可能出现几个汇编指令组合起来实现C语言语句的情况。
练习:
下图给出了C语言函数及对应的汇编代码,其中C语言实现的部分是空缺的,我们需要根据汇编代码填充C语言代码的功能:
- x^y
- t1>>3
- ~t2
- t3-z
1.5 特殊的算术运算
下图展示了支持用两个32位值生成64位结果的操作数,同时包括了除法运算: