当使用像Golang、Java、Python、C# 这些高级语言的时候,编译器为我们生成了操作机器的指令,屏蔽了程序的许多细节。不过我们依然可以通过查看汇编代码,了解其中的奥秘。
Go语言为我们提供了可以查看汇编代码的工具:
1
go tool compile -S main.go
提示:Go1.20以及之后的版本为了减小Go发行版的大小,默认不再安装标准库的预编译包,需要执行以下命令自行安装。
1
GODEBUG=installgoroot=all go install std
否则若依赖到标准库,会抛出类似以下异常:
1
main.go:3:8: could not import fmt (open fmt.a: no such file or directory)
先来看下一段空白的代码生成的汇编指令:
1
2
3
4
//main.go
package main
func main() {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go tool compile -S main.go
//Output:
"".main STEXT nosplit size=1 args=0x0 locals=0x0 funcid=0x0
0x0000 00000 (main.go:3) TEXT "".main(SB), NOSPLIT|ABIInternal, $0-0
0x0000 00000 (main.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:8) RET
0x0000 c3 .
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
0x0000 6d 61 69 6e main
""..inittask SNOPTRDATA size=24
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010 00 00 00 00 00 00 00 00 ........
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
0x0000 01 00 00 00 00 00 00 00 ........
再来看另外一段简单的代码,计算a与b的和c,然后忽略c:
1
2
3
4
5
6
7
8
//main.go
package main
func main() {
a := 1
b := 2
c := a + b
_ = c
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go tool compile -S main.go
//Output:
"".main STEXT nosplit size=1 args=0x0 locals=0x0 funcid=0x0
0x0000 00000 (main.go:3) TEXT "".main(SB), NOSPLIT|ABIInternal, $0-0
0x0000 00000 (main.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:8) RET
0x0000 c3 .
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
0x0000 6d 61 69 6e main
""..inittask SNOPTRDATA size=24
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010 00 00 00 00 00 00 00 00 ........
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
0x0000 01 00 00 00 00 00 00 00 ........
有没有发现,两段代码生成的汇编指令一模一样,那第二段代码中我们创建的变量哪里去了?
第二段代码计算了 c := a + b,然后使用 _ = c 忽略了c,实际上这些操作都可以看着无效代码,编译器自动我们做了优化处理。
可以通过指定 -N 参数禁止编译器进行优化, -l 参数禁止内联代码,再来看一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
go tool compile -N -l -S main.go
//Output:
"".main STEXT nosplit size=55 args=0x0 locals=0x20 funcid=0x0
0x0000 00000 (main.go:3) TEXT "".main(SB), NOSPLIT|ABIInternal, $32-0
0x0000 00000 (main.go:3) SUBQ $32, SP
0x0004 00004 (main.go:3) MOVQ BP, 24(SP)
0x0009 00009 (main.go:3) LEAQ 24(SP), BP
0x000e 00014 (main.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (main.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (main.go:4) MOVQ $1, "".a+16(SP)
0x0017 00023 (main.go:5) MOVQ $2, "".b+8(SP)
0x0020 00032 (main.go:6) MOVQ "".a+16(SP), AX
0x0025 00037 (main.go:6) ADDQ $2, AX
0x0029 00041 (main.go:6) MOVQ AX, "".c(SP)
0x002d 00045 (main.go:8) MOVQ 24(SP), BP
0x0032 00050 (main.go:8) ADDQ $32, SP
0x0036 00054 (main.go:8) RET
0x0000 48 83 ec 20 48 89 6c 24 18 48 8d 6c 24 18 48 c7 H.. H.l$.H.l$.H.
0x0010 44 24 10 01 00 00 00 48 c7 44 24 08 02 00 00 00 D$.....H.D$.....
0x0020 48 8b 44 24 10 48 83 c0 02 48 89 04 24 48 8b 6c H.D$.H...H..$H.l
0x0030 24 18 48 83 c4 20 c3 $.H.. .
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
0x0000 6d 61 69 6e main
""..inittask SNOPTRDATA size=24
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010 00 00 00 00 00 00 00 00 ........
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
0x0000 01 00 00 00 00 00 00 00 ........
可以看到main.go 第4到6 行生成的汇编指令:
1
2
3
4
5
0x000e 00014 (main.go:4) MOVQ $1, "".a+16(SP) ;将数值1赋值给变量a。(SP寄存器,栈)
0x0017 00023 (main.go:5) MOVQ $2, "".b+8(SP) ;将数值2赋值给变量b。(SP寄存器,栈)
0x0020 00032 (main.go:6) MOVQ "".a+16(SP), AX ;将变量a的值传递给 AX 寄存器
0x0025 00037 (main.go:6) ADDQ $2, AX ;将数值2与AX寄存器中的值累加,并将结果保存到AX寄存器。
0x0029 00041 (main.go:6) MOVQ AX, "".c(SP) ;将AX寄存器中的值赋值给变量c。
说明一下:
””. 指的是这个函数或变量所在的命名空间。 mov 是一个在汇编语言中常见的指令,用来赋值。mov a b 一般是将b赋值给a。 movq(64位) 在x86-64下,需要将源与目的反过来看。 $1, $2 操作数,数字前加上$,表示这个数值本身。
还可以发现代码中的 _ = c,编译器并没有为它生成汇编指令。