From ced4ed0696729c7b455f2ff0e50010bb96aee6d9 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Thu, 7 Jun 2018 06:47:03 +0800 Subject: [PATCH] =?UTF-8?q?ch3-05:=20=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ch3-asm/ch3-05-control-flow.md | 53 ++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/ch3-asm/ch3-05-control-flow.md b/ch3-asm/ch3-05-control-flow.md index 6e215e1..d42bc4d 100644 --- a/ch3-asm/ch3-05-control-flow.md +++ b/ch3-asm/ch3-05-control-flow.md @@ -95,7 +95,8 @@ TEXT ·main(SB), $16-0 MOVQ AX, temp-8(SP) // temp = AX // 以a为参数调用函数 - // ... + CALL runtime·printint + CALL runtime·printnl // 函数调用后, AX 可能被污染, 需要重新加载 MOVQ temp-8*1(SP), AX // AX = temp @@ -110,13 +111,55 @@ TEXT ·main(SB), $16-0 首先是将main函数的栈帧大小从24字节减少到16字节。唯一需要保存的是a变量的值,因此在调用runtime·printint函数输出时全部的寄存器都可能被污染,我们无法通过寄存器备份a变量的值,只有在栈内存中的值才是安全的。然后在BX寄存器并不需要保存到内存。其它部分的代码基本保持不变。 -## goto跳转 +## if/goto跳转 -TODO +早期的Go虽然提供了goto语句,但是并不推荐在编程中使用。有一个和cgo类似的原则:如果可以不使用goto语句,那么就不要使用goto语句。Go语言中的goto语句是有严格限制的:它无法跨越代码块,并且在被跨越的代码中不能含义变量定义的语句。虽然Go语言不喜欢goto,但是goto确实每个汇编语言码农的最爱。goto近似等价于汇编语言中的无条件跳转指令JMP,配合if条件goto就组成了有条件跳转指令,而有条件跳转指令正是构建整个汇编代码控制流的基石。 -## if分支 +为了便于理解,我们用Go语言构造一个模拟三元表达式的If函数: -TODO +```go +func If(ok bool, a, b int) int { + if ok { return a } else { return b } +} +``` + +比如求两个数最大值的三元表达式`(a>b)?a:b`用If函数可以这样表达:`If(a>b, a, b)`。因为语言的限制,用来模拟三元表达式的If函数不支持范型(可以将a、b和返回类型改为空接口,使用会繁琐一些)。 + +这个函数虽然看似只有简单的一行,但是包含了if分支语句。在改用汇编实现前,我们还是先用汇编的思维来重写If函数。在改写时同样要遵循每个表达式只能有一个运算符的限制,同时if语句的条件部分必须只有一个比较符号组成,if语句的body部分只能是一个goto语句。 + +用汇编思维改写后的If函数实现如下: + +```go +func If(ok int, a, b int) int { + if ok == 0 { goto L } + return a +L: return b +} +``` + +因为汇编语言中没有bool类型,我们改用int类型代替bool类型。当ok参数非0时返回变量a,否则返回变量b。我们将ok的逻辑反转下:当ok参数为0时,表示返回b,否则返回变量a。在if语句中,当ok参数为0时goto到L标号指定的语句,也就是返回变量b。如果if条件不满足,也就考试ok残非0,执行后门的语句返回变量a。 + +上述函数的实现已经非常接近汇编语言,下面是改为汇编实现的代码: + +``` +TEXT ·If(SB), NOSPLIT, $0-32 + MOVQ ok+8*0(FP), CX // ok + MOVQ a+8*1(FP), AX // a + MOVQ b+8*2(FP), BX // b + + CMPQ CX, $0 // test ok + JZ L // if ok == 0, skip 2 line + MOVQ AX, ret+24(FP) // return a + RET + +L: + MOVQ BX, ret+24(FP) // return b + RET +``` + +首选是将三个参数加载到寄存器中,ok参数对应CX寄存器,a、b分别对应AX、BX寄存器。然后使用CMPQ比较指令将CX寄存器和常数0进行比较。如果比较的结果为0,那么下一条JZ为0时跳转指令将跳转到L标号对应的指令,也就是返回变量b的值。如果比较的结果不为0,那么JZ指令讲没有效果,继续执行后的指令,也就是返回变量a的值。 + +在跳转指令中,跳转的目标一般是通过一个标号表示。不过在有些通过宏实现的函数中,更希望通过相对位置跳转,这时候可以通过PC寄存器的来计算跳转的位置。 ## for循环