3.27
要求你将书中的阶乘函数,利用guarded-do的翻译策略,转换成c的goto版本,答案如上图。
注意第一次测试为if(n <= 1)
,这是因为,第一次测试实际是2 <= n
,它的反面是2 > n
即n < 2
即n <= 1
。
3.28 反转x的二进制
题面上的汇编如上,在用汇编推理c语句时,对于for循环的测试条件,我刚开始没有想通,因为没有看见cmp指令。
原理:jne .L10
是检测ZF标志位的,与它最近的语句subq $1, %rdx
是有可能置ZF为1的,当%rdx为0时。
long fun_b(unsigned long x){
long val = 0;
long i;
for(i = 64; i != 0; i--){
val = (val << 1) | (x & 0x1);
x >>= 1;
}
return val;
}
此函数用来获得x的二进制的反转。
x & 0x1
获得当前x的第0位的数。val << 1
将已经存储的二进制数移到更高一位上去。x >> 1
既然当前x的第0位已经存储在了val,那么便丢弃掉第0位,右移1位,把第1位的数作为新的第0位的数。
3.7.5 寄存器中的局部存储空间
除了栈指针%rsp外,寄存器分为被调用者保存寄存器,和调用者保存寄存器。
在过程P调用Q时,如果值放在了被调用者保存寄存器中,那么它们的值在Q返回到P时,与P刚调用Q时,是一样的。Q如何保证这些值不变(要么不去碰它,要么暂时先放入栈帧中),对于P来说不用知道,因为对于P来说是透明的,P只需要在调用Q之前,将需要存储的值放入这些被调用者保存寄存器即可。
对于如上汇编代码,有三点需要注意:
1.两次push和两次pop。pushq %rbp
和pushq %rbx
先将寄存器中的值放入栈中,注意两个都是被调用者保存寄存器。也许你会觉得奇怪,第5和第8行已经按照要求,在调用Q之前把需要存储的值存入到了%rbp %rbx
,为什么还要把%rbp %rbx
的刚开始的值入栈存起来呢。
这是因为,刚开始的时候%rbp %rbx
这两个寄存器可能就已经存了需要保存的值,但因为调用Q所以%rbp %rbx
要暂时另作它用。换个角度说,当前的调用者P可能是别的过程的被调用者。这也就是原文中的“当然,要先把之前的值保存到栈上”这句话的意思。
2.为了存调用返回地址,需要手动分配栈空间。subq $8, %rsp
这里分配的8字节栈空间,存了两次调用返回地址(分别是第8行和第11行的指令的地址),这里的8字节栈空间是被第二次利用了。
3.注意pushq %rbp
和pushq %rbx
和subq $8, %rsp
的顺序,它们与后面第12、13、14行指令的顺序,这里是符合栈的先进后出的。
栈指针%rsp
阅读了这么多汇编代码后,总结下栈指针%rsp。
1.栈指针%rsp它其实不算是个寄存器,顾名思义,它是一个指针,指向当前栈顶地址。
2.比如%rax
,它一个普通的寄存器,取%rax
时,就会取它装有的8字节数据,可能它是个指针;取(%rax)
时,则成了内存引用,会取出这个指针指向的数据;
而%rsp
,它是栈指针,取%rsp
时,返回的是当前栈指针的地址形如0x7fffffffe820
;取(%rsp)
时,则可能取出从820-827这8个字节里面存的数据;而一般来说,我们不需要取%rsp
,因为要栈顶地址来根本没用啊。