猜,下面程序会输出什么?html
func main() { i := 7758 j := i++ fmt.Println(i,j) }
在没有遇到过以前,笔者也以为这是大学生的期末考试题,认为该程序会输出7759 7758,由于i++常规操做是先用后加,因此j就是7758,i就是7759。golang
可是正确答案是会报错,确切的说这段程序在编译期间就会出错,将这段代码放到IDE就会发现爆红。浏览器
这是由于Go中的i++不一样于C中的i++,Go中的i++是语句,而C中的i++则是表达式。谈下我所理解的表达式和语句的区别:函数
表达式是一段能够被求值的代码,也就是能够有接收者;而语句是能够被执行的代码,不必定会有接收者。从上面的例子来看,Go中的i++是语句,它不能有接收者,至关于一条能够被编译器识别的命令,相似于break,goto这种语句,因此在程序在编译期间就会报错。字体
既然原理不一样,笔者就想经过汇编来对比下C的i++与Go的i++二者有什么不一样点。不要听到汇编就劝退哦,笔者列举的都是很简单的语句。优化
接下来简化下程序,只保留一个声明和一个自增。ui
//C语言示例 #include <stdlib.h> int main(){ int i = 7758; i++; } //Go语言示例 package main func main() { i := 7758 i++ }
先把C语言反汇编看下,看下主要部分,能够看到自增的过程,以下:spa
$ gcc -o plusplustestc -g plusplus.c $ objdump -S plusplus ...... int i = 7758; movl $0x1e4e,-0x4(%rbp) #将7758赋值到rbp寄存器 i++; addl $0x1,-0x4(%rbp) #将rbp寄存器加1 ......
再把Go反汇编看下,发现了奇怪的现象,为了产生对比效果,我也使用objdump生成汇编语句,发现这里直接用自增后的7759覆盖了先前的7758,而这之间并无计算过程。设计
$ go build --gcflags="-l -N" -o plusplustestgo plusplus.go $ objdump -S plusplustestgo ...... i := 7758 movq $0x1e4e,(%rsp) #将7758赋值到rsp寄存器 i++ movq $0x1e4f,(%rsp) #将7759赋值到rsp寄存器 ......
这是由于Go的编译器作了优化,咱们看到的Plan9汇编这些,都是在编译最后阶段生成的,在这中间编译器作了大量的优化,省去了许多无用代码(dead code),好比上述代码就是Go编译器SSA(Static Single Assignment静态单赋值)作的优化,Go语言编译器在将.go文件编译为机器码过程当中会生成几十个版本的中间代码,中间会伴随着代码优化,删除不会被用到的片断,而上述程序的7758自增为7759的过程就被编译器“优化”了,只保留将7759覆盖到寄存器的过程。code
咱们可使用GOSSAFUNC环境变量构建从源代码到机器码这中间几十次中间代码的迭代过程,该方法最后会生成ssa.html文件,便于用户查看,方法以下:
这里仍然用原来的Go文件示例。
package main func main() { i := 7758 i++ }
接下来进入该文件的同级目录下,这里可能要切换至root权限,执行命令
# 命令以下 # GOSSAFUNC=<函数名> go build <.go文件> # 实际执行 $ GOSSAFUNC=main go build plusplus.go # runtime dumped SSA to /usr/local/go-1.14/src/runtime/ssa.html # command-line-arguments dumped SSA to ./ssa.html
此时中间代码已经生成到了ssa.html文件中,咱们用浏览器打开。能够经过点击红框中的字体查看每一步中间码的生成,也能够点击任意一行代码查看中间代码转化关系。
上面俩图中间还有一长串的中间代码,这里就不贴了。
在这里浅色的字体表明被编译器”优化“的代码即dead code,这些代码不会被编进最后的机器码中。
可能有些细心的同窗会发现,这里最终编出来的机器码genssa中也没有我上述贴的代码中赋值寄存器的操做啊,并且怎么,为何会形成不一致呢?
这就是 -gcflags="-l -N" 的做用了,在上面生成汇编时候加了这个参数防止内联(-l)以及编译优化(-N),因此咱们能够看到对寄存器赋值的语句。一样的,咱们也能够在SSA生成时候加上这个参数,这样,一些中间代码就不会被优化掉了,就能够看到对应的中间代码了,以下。
$ GOSSAFUNC=main go build --gcflags="-l -N" plusplus.go # runtime dumped SSA to /usr/local/go-1.14/src/runtime/ssa.html # command-line-arguments dumped SSA to ./ssa.html
此时再次查看生成的ssa.html,就是禁止内联以及编译优化的机器码的生成步骤了,感兴趣的同窗能够本身尝试下。