《深入理解计算机系统》 练习题4.4-4.5
4.4
用Y86-64来实现一个递归求和函数:如下
long rsum(long *start, long count) {
if (count <= 0)
return 0;
return *start + rsum(start+1,count-1);
}
没想出来答案,但能看明白答案==
下面为x86-64的代码:
1.表面上看起来,每次进入递归函数都会movl $0, %eax
使得返回值寄存器;实际上,是每次递归除了最后一次的递归终点,都会先执行函数体的前半段(以call rsum
分开),即每次执行函数体前半段都会使得%rax为0,但这并不会产生错误影响(虽然我觉得有点多余,把movl $0, %eax
放在.L9代码块里不好吗,就只需要做一次了)。执行了最后一次即递归终点后,返回上一层函数,执行上一层函数的后半段,以后对%rax产生操作就只有addq指令了。
2.pushq %rbx
,对于上一层函数来说,当前函数为被调用者,作为被调用者,就要做好被调用者的工作,把上一层函数保存在%rbx的值入栈存起来,然后再放心大胆地使用%rbx。
3.%rbx一直存的都是当前数组指针指向的值。
下面为Y86-64的代码:
有一些差别呢:
清零用的xorq;测试用的andq;跳转用的je,毕竟为0就是终止条件。
下面为MinGW编译得到的代码,使用命令为gcc -Og -S test.c
:
.file "test.c"
.text
.globl _rsum
.def _rsum; .scl 2; .type 32; .endef
_rsum:
LFB0:
.cfi_startproc
pushl %ebx # 将被调用者保存寄存器%ebx的值入栈存起来
.cfi_def_cfa_offset 8
.cfi_offset 3, -8
subl $24, %esp # 分配了24字节的栈空间
.cfi_def_cfa_offset 32
movl 32(%esp), %ecx # 从32开始,跳过了之前入栈存的值所在的8字节;32与36相差4,一个地址只需要4字节存储,是数组元素的地址
movl 36(%esp), %edx # 36处存储count的地址,取出count放入%edx
testl %edx, %edx # %edx是count
jle L3 # 大于0的正常情况即递归过程,会走下面这块代码
movl (%ecx), %ebx # 取出数组元素指针指向的值放入%ebx中
subl $1, %edx # count减一
movl %edx, 4(%esp) # 在新分配栈空间存新的count
addl $4, %ecx # 数组元素指针减四
movl %ecx, (%esp) # 在新分配栈空间存新的start
call _rsum
addl %ebx, %eax
L1:
addl $24, %esp #收回栈空间
.cfi_remember_state
.cfi_def_cfa_offset 8
popl %ebx #取出保存的值
.cfi_restore 3
.cfi_def_cfa_offset 4
ret
L3:
.cfi_restore_state
movl $0, %eax #这句只会执行一次,进行了优化
jmp L1
.cfi_endproc
LFE0:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
一直对MinGW编译得到的汇编有点疑问,比如第一个参数和第二个参数为什么不用%rdi和%rsi,但这里先忽略不计吧。
1.%ecx存的是数组元素指针,%ebx存的是数组元素,%edx存的是count。
2.编译器认为long型变量是4字节的。
3.从movl 32(%esp), %ecx
和movl 36(%esp), %edx
,可以看出编译器认为指针只需要4字节存储,32与36相差4可以看出,且用4字节的%ecx寄存器来存指针,也可以看出。
4.解释一下为什么分配了24栈空间,又要从32地址偏移处取变量,用下图解释:
这是因为保存%ebx的值时用了8字节,栈空间又分配了24字节,所以要想取到上一个栈帧中的变量,就得偏移32字节。
上图过程可以一直进行下去。
4.5
答案看懂了,但是有点奇怪,明明都使用条件跳转了,还非得要在loop里面先把-x给算出来;先判断x的正负,如果需要再计算-x这样不好吗?所以我觉得loop部分应该这样改写:
loop:
mrmovq (%rdi), %r10
jge pos # 如果x小于0那么将执行下面三行
xorq %r11, %r11 # 接下来三行将%r10赋值为-x
subq %r10, %r11
rrmovq %r11, %r10
另外取负指令是neg
;也可以通过每位取反再加1,取反指令是not
。怕是Y86-64里面没有这两个指令吧。