指令类ADD由三条加法指令组成:addb, addw, addl, 分别是字节加法,字加法和双字加法。
事实上,每个指令类都有对字节,字和双字数据进行操作的指令。这些操作被分为四组:加载有效地址,一元操作,二元操作,移位。
加载有效地址:
加载有效地址(load effective address)指令leal实际上是movl的变形。它的指令形式是从存储器读数据到寄存器,但实际上没有引用存储器。它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。
eg:
如果寄存器%eax的值为x,那么指令leal 7(%edx, %edx, 4),%eax将设置寄存器%eax的值为5x+7.
加载有效地址指令(leal)通常用来执行简单的算术操作。
一元操作和二元操作:
一元操作:只有一个操作数,既是源又是目的,这个操作数可以是一个寄存器,也可以是一个存储器位置。
eg:指令incl(%esp)会使栈顶的4字节元素加1,与C语言的自增,自减操作符有点类似。
二元操作:第二个操作数既是源又是目的,与C语言的赋值运算符类似,例如x+=y;不过,要注意,源操作数是第一个,目的操作数是第二个。
eg:指令subl %eax, %edx
使寄存器%edx的值减去%eax的值。
第一个操作数可以是立即数,寄存器或者存储器位置,第二个操作数可以是寄存器或者存储器位置。和movl指令一样,两个操作数不能同时是存储器位置。
移位操作:
移位操作,先给出移位量,然后第二项给出要移位的位数,它可以进行算数和逻辑右移。移位量用单个字节编码,因为只允许0到31位的移位(只考虑移位量的低五位)。
移位量可以是一个立即数,或者放在单字节寄存器元素%cl中。
左移指令有两个名字:SAL和SHL,两者效果一样,都是将右边填上0;右移指令不同,SAR执行算术移位(填上符号位),而SHR执行逻辑移位(填上0),移位操作的目的操作数可以是一个寄存器或者是一个存储器位置。
下来看一个执行算术操作的函数:
源代码:
int arith(int x, int y, int z)
{
int t1 = x + y;
int t2 = z * 48;
int t3 = t1 & 0xFFFF;
int t4 = t2 * t3;
return t4;
}
汇编代码:
汇编指令与源代码中顺序不同。指令4和指令5用leal和移位指令的组合来实现表达式z*48,第七行计算x+y的值,第八行计算t1和0xFFFF的AND值,第八行执行最后的乘法。
特殊的算术操作:
imull指令:“双操作数”乘法指令,从两个32位操作数产生一个32位乘积。(补码乘法)
mull指令:无符号数乘法
这两个指令都要求一个参数必须在寄存器%eax中,而另一个作为指令的源操作数给出,然后乘机存放在寄存器%edx(高32位)和%eax(低32位)中。
eg:假设有符号数x和y存储在相对于%ebp偏移量为8和12的位置,我们希望把他的全64位乘积作为8个字节存放在栈顶。代码如下:
movl 12(%ebp), %eax
imull 8(%ebp)
movl %eax, (%esp)
movl %edx, 4(%esp)
小端机器:寄存器%edx中的高位存放在相对于%eax中的低偏移量为4的地方。栈是向低地址方向增长的,也就是说低位在栈顶。
Idivl:有符号除法指令将寄存器%edx(高32位)和%eax(低32位)中的64位数作为被除数,而除数作为指令的操作数给出。指令将商存储在寄存器%eax中,将余数存储于寄存器%edx中
设置除数更常规的办法是使用cltd指令,这条指令将%eax符号扩展到%edx。
无符号除法指令使用divl指令,通常事先将%edx设置为0.