汇编语言特殊算数操作
文章目录
首先纠错
书上除法这里是有错误的,商和余数都放在了R[%rdx]
这显然是不合理的,我觉得应该写为:
乘法
c源文件
#include <inttypes.h>
typedef unsigned __int128 uint128_t;
//此处改名的目的是让128位整数能够类似64位整数使用
void store_uprod(uint128_t *dest,uint64_t x,uint64_t y){
*dest=x*(uint128_t) y;
}
汇编语言
movq %rsi, %rax
mulq %rdx
movq %rax, (%rdi)
movq %rdx, 8(%rdi)
ret
逻辑分析:
首先三个参数分别存放在
即
R[%rdi]=dest//这里%rdi寄存器中存放的是dest这个位置 dest这个位置 dest这个位置
R[%rsi]=x
R[%rdx]=y
1.movq %rsi, %rax
即令R[%rax]=R[%rsi]=x
将x放在了%rax
寄存器中
2. mulq %rdx
R[%rdx]=y
看似只有一个操作数,实际上隐含着另一个操作数在%rax
寄存器中
即令R[%rdx]*R[%rax]
,两个64位数的计算机结果是一个128位数,显然单独一个64位寄存器是放不下的,因此计算结果被分成两部分
低64位放在%rax
寄存器,高64位放在%rdx
寄存器
3. movq %rax, (%rdi)
R[%rdi]=dest
这是指针指向内存中的位置
M[R[%rdi]]=M[dest]
对内存中的位置dest应用解引用函数
M
[
d
e
s
t
]
M[dest]
M[dest]得到的是该地址上的实际数值
令M[R[%rdi]]=R[%rax]
,将%rax
寄存器中刚刚算出的低64位结果放在内存中,位置为R[%rdi]
(间接寻址)
4. movq %rdx, 8(%rdi)
刚才在第3步时存放了低64位,那么此时应该做到就是存放高64位
令M[R(%rdi)+8]=R[%rdx]
,将%rdx
寄存器中刚刚算出的高64位结果放在内存中,位置为R(%rdi)+8
(基址+偏移量寻址)
此处的+8为偏移8个字节,因为低地址恰好占用这8个字节
在小端机器上低地址存放低位,高地址存放高位
到此乘法的计算结果已经被分成两个64位数存进了一个uint128_t *
指针指向的地址
除法
c源文件
void remdiv(long x,long y,long *qp,long *rp){//希望计算x/y将商存到指针qp指向的地址,将余数存到指针rp指向的地址
long q=x/y;
long r=x%y;
*qp=q;
*rp=r;
}
汇编语言
movq %rdi, %rax
movq %rdx, %r8
cqto
idivq %rsi
movq %rax, (%r8)
movq %rdx, (%rcx)
ret
R[%rdi]=x
R[%rsi]=y
R[%rdx]=qp
R[%rcx]=rp
1. movq %rdi, %rax
令R[%rax]=R[%rdi]=x
将x存放在%rax
寄存器里
2. movq %rdx, %r8
令R[%r8]=R[%rdx]=qp
将商在内存中的地址存放在%r8
寄存器里
对于一个128位数的除法运算,被除数的低64位存放在%rax
寄存器中,高64位存放在%rdx
寄存器中
刚才在第1步时已经将64位数x放在%rax
寄存器中了,但是高64位所在的%rdx
现在被第三个参数qp
占用,因此将qp
放在另一个寄存器%r8
中,然后将%rdx
腾出来方便存放高64位
3. cqto
为什么要进行符号拓展?
被除数应该是一个128位数,但是目前我们只是确定了其低64位为x,高64位还是第三个参数的值没有修改,如果此时直接计算则高64位的值可以认为是乱码,那么怎么消除高64位的乱码呢?置零或者置符号,我们将要进行符号除法,因此高64位置符号
即高64位按照R[%rax]=x
的符号位拓展
4. idivq %rsi
R[%rsi]=y
R[%rdx]=x mod y
R[%rax]=x/y
5. movq %rax, (%r8)
R[%r8]=qp
M[R[%r8]]=M[qp]=*qp=R[%rax]=x/y
6.movq %rdx, (%rcx)
R[%rcx]=rp
M[R[%rcx]]=M[rp]=*rp=R[%rdx]=x mod y
习题3.12
首先四个参数的存放位置为:
执行除法的时候只会提供一个操作数S作为除数,表示被除数的另两个操作数是隐含的%rax,%rdx
那么在执行除法命令之前,应该把被除数先安置好
1.首先128位被除数的低64位存放在%rax
中,即R[%rax]=x
2.然后高64位存放在%rdx
中,无符号除法时应当全置0,
但是由于第三个参数qp已经占据了%rdx
,因此在将其全都置零之前应当请三个参数挪个地方,比如%r8
movq %rdx,%r8
3.此时就可以将被除数的高64位%rdx
寄存器置0了,最直接的置零方法是movq $0,%rdx
,还可以利用异或的性质xorq %rdx,%rdx
至于应该选择哪一个?应该选择二进制长度最短的指令
0000000000000000 <.text>: 0: 48 c7 c2 00 00 00 00 mov $0x0,%rdx 7: 48 99 cqto 9: 48 31 d2 xor %rdx,%rdx
由此可见为什么刚才有符号除法时要用cqto,因为其长度最短
然后xor也是不错的选择
最迫不得已才会选择movq指令
当发现实际编译器使用的命令与我们理想的不一样时,可以写一个.s文件然后将自己理想的汇编指令和实际的汇编指令各写一行
然后使用
gcc -Og -c
命令,使其编译成为.o文件,注意必须指定-c
选项,否则直接编译成.exe或者.out会发生链接错误,因为刚才我门写的.s文件是非常不完整的,连main函数都没有然后对.o文件使用objdump命令反编译 ,就可以观察指令及其二进制编码长度了
一般理想与现实不同都是由于有更加短但是可以完成同样目的的指令我们没有考虑到
在本题中我不知道类似cqto
但是是无符号拓展的指令,可以先用异或指令达到相同的目的
4.被除数在3中已经准备好了,可以进行除法了
这里S是除数,本题中除数是第二个参数y存放在%rsi
寄存器中,即R[%rsi]=y
那么除法指令为divq %rsi
5.除法进行完毕,瓜分商和余数
商位于%rax
寄存器中,希望传送到内存中的qp位置,而内存中的qp位置存放在寄存器%r8
中(即R[%r8]=qp,M[R[%r8]]=*qp
)因此使用指令movq %rax,(%r8)
余数位于%rdx
寄存器中,希望传送到内存中的rp位置,而内存中的rp位置存放在寄存器%rcx
中(即R[%rcx]=rp,M[R[%rcx]]=*rp
)因此使用指令movq %rdx,(%rcx)
外链图片转存失败的臭毛病能不能改啊,非得手动一张张上传