mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 12:32:21 +00:00
ch3-05: 完善
This commit is contained in:
parent
b58f647e2f
commit
ced4ed0696
@ -95,7 +95,8 @@ TEXT ·main(SB), $16-0
|
|||||||
MOVQ AX, temp-8(SP) // temp = AX
|
MOVQ AX, temp-8(SP) // temp = AX
|
||||||
|
|
||||||
// 以a为参数调用函数
|
// 以a为参数调用函数
|
||||||
// ...
|
CALL runtime·printint
|
||||||
|
CALL runtime·printnl
|
||||||
|
|
||||||
// 函数调用后, AX 可能被污染, 需要重新加载
|
// 函数调用后, AX 可能被污染, 需要重新加载
|
||||||
MOVQ temp-8*1(SP), AX // AX = temp
|
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寄存器并不需要保存到内存。其它部分的代码基本保持不变。
|
首先是将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循环
|
## for循环
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user