汇编指令之算术和逻辑操作指令

阅读更多
        下表是 x86-64 中的一些整数和逻辑操作指令,其中除 leaq 指令外,其他指令类都有各种不同大小操作数的变种(类似于 MOV 类指令,见 汇编指令之数据传送指令)。例如,指令类 ADD 由四条加法指令组成:addb、addw、addl 和 addq,分别是字节加法、字加法、双字加法和四字加法。这些操作被分为四组:加载有效地址、一元操作、二元操作和移位。
汇编指令之算术和逻辑操作指令_第1张图片
        加载有效地址(load effective address)指令 leaq 实际上是 movq 指令的变形。虽然它的指令形式是从内存读取数据到寄存器,但实际上它根本没有引用内存。它的第一个操作数看上去是一个内存引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数(必须是一个寄存器)。这条指令可以为后面的内存引用产生指针,而且它还可以简洁地描述普通的算术操作。例如,如果寄存器 %rdx 的值为 x,那么指令“leaq 7(%rdx, %rdx, 4), %rax”将把寄存器 %rax 的值设置为 5x+7。
        第二组中的操作数是一元操作,只有一个操作数,既是源又是目的。这个操作数可以是一个寄存器,也可以是一个内存位置。比如,指令“incq(%rsp)”会使栈顶的 8 字节元素加 1,类似于 C 语言中的自增运算符。
        第三组是二元操作,其中,第二个操作数既是源又是目的。类似于 C 语言中的赋值运算符,如 x-=y。第一个操作数可以是立即数、寄存器或是内存位置,第二个操作数可以是寄存器或是内存位置。当第二个操作数是内存地址时,处理器必须从内存读出值,执行操作,再把结果写回内存。
        最后一组是移位操作:先给出移位量,然后第二项给出的是要移位的数。移位量可以是一个立即数,或者放在特定的单字节寄存器 %cl 中。x86-64 中,移位操作对 w 位长的数据值进行操作,移位量是由 %cl 寄存器的低 m 位决定的,这里 2^m=w,高位则会被忽略。例如,当 %cl 寄存器的值为 0xFF 时,指令 salb 会移 7 位,salw 会移 15 位,sall 会移 31 位,而 salq 会移 63 位。移位操作的目的操作数可以是一个寄存器或是一个内存位置。
        上面这些都属于常规的算术指令,其实还有一类特殊的算术操作。通常,两个 64 位有符号或无符号整数相乘得到的乘积需要 128 位(16字节,称为八字)来表示,x86-64 指令集对此提供了有限的支持,如下表所示。
汇编指令之算术和逻辑操作指令_第2张图片
        注意,这里又出现了一个 imulq 指令,不过它是单操作数的,汇编器能够通过计算操作数的数目来分辨出想用哪条指令。单操作数的 imulq 指令和 mulq 指令分别是计算两个 64 位值的全 128 位乘积,前者计算补码乘法,后者计算无符号数乘法。它们都要求一个参数必须在寄存器 %rax 中,而另一个作为指令的源操作数给出,然后乘积存放在寄存器 %rdx(高 64 位)和 %rax(低 64 位)中。下面这段 C 代码说明了如何从两个无符号 64 位数字 x 和 y 生成 128 位的乘积。
#include 

typedef unsigned __int128 uint128_t;

void store_uprod(uint128_t *dest, uint64_t x, uint64_t y){
    *dest = x * (uint128_t)y;
}

        这里的 unsigned __int128 类型是 GCC 提供的 128 位整数支持,改为 uint128_t 类型别名是为了沿用 inttypes.h 中其他数据类型的命名规律。最终的乘积存放在指针 dest 指向的 16 字节处。这段代码对应的汇编代码大致如下(对应的是小端法机器)。
; void store_uprod(uint128_t *dest, uint64_t x, uint64_t y)
; dest in %rdi, x in %rsi, y in %rdx
store_uprod:
    movq    %rsi, %rax        ; Copy x to multiplicand
    mulq    %rdx              ; Multiply by y
    movq    %rax, (%rdi)      ; Store lower 8 bytes at dest
    movq    %rdx, 8(%rdi)     ; Store upper 8 bytes at dest+8
    ret

        除了 64 位乘法指令,这里还提供了之前的算术表里没有的除法和取模操作。它们也是单操作数的。其中,有符号除法指令 idivq 将寄存器 %rdx(高 64 位)和 %rax(低 64 位)中的 128 位数作为被除数,而将操作数作为除数,最终得出的商存储在寄存器 %rax 中,余数则存储在寄存器 %rdx 中。对于 64 位的被除数,其值应该存放在寄存器 %rax 中,而把 %rdx 的位设置为全 0(无符号运算)或者 %rax 的符号位(有符号运算)。后面这个操作可以用图中的指令 cqto 来完成,该指令不需要操作数,它会隐含读出 %rax 的符号位,并将它复制到 %rdx 的所有位。
        下面这个 C 函数说明了 x86-64 是如何实现除法的,它计算了两个 64 位有符号数的商和余数。
void remdiv(long x, long y, long *qp, long *rp){
    long q = x / y;
    long r = x % y;
    *qp = q;
    *rp = r;
}

        该函数对应的汇编代码大致如下。
; void remdiv(long x, long y, long *qp, long *rp)
; x in %rdi, y in %rsi, qp in %rdx, rp in %rcx
remdiv:
    movq    %rdx, %r8        ; Copy qp
    movq    %rdi, %rax       ; Move x to lower 8 bytes of dividend
    cqto                     ; Sign-extend to upper 8 bytes of dividend
    idivq   %rsi             ; Divide by y
    movq    %rax, (%r8)      ; Store quotient at qp
    movq    %rdx, (%rcx)     ; Store remainder at rp
    ret

  • 汇编指令之算术和逻辑操作指令_第3张图片
  • 大小: 123.8 KB
  • 汇编指令之算术和逻辑操作指令_第4张图片
  • 大小: 123.5 KB
  • 查看图片附件

你可能感兴趣的:(汇编,算术指令,逻辑指令,传送指令)