i++识字猜出来的那些东西,下面的程序会输出什么?funcmain(){i:=7758j:=i++fmt.Println(i,j)}遇到之前笔者也以为这是一道大学生期末考试题。我觉得程序会输出77597758,因为i++正常运行是先用后加,所以j是7758,i是7759。但是正确答案是会报错。准确的说,这个程序在编译的时候会出错。如果将这段代码放在IDE中,您会发现它很受欢迎。这是因为Go中的i++与C中的i++不同,Go中的i++是一个语句,而C中的i++是一个表达式。先说说我理解的表达式和语句的区别:表达式是一段可以求值的代码,也就是可以有接收者;而语句是可以执行的代码,它不一定有接收者。从上面的例子来看,Go中的i++是一个语句,不能有接收者。它相当于一个编译器可以识别的命令,类似于break和goto语句,所以在程序编译过程中会报错。由于原理不同,笔者想通过汇编比较一下C的i++和Go的i++的区别。听到编译不要气馁,列出的作者都是很简单的句子。与汇编相比,程序接下来进行了简化,只保留了一条语句和一条自增。//C语言示例#includeintmain(){inti=7758;i++;}//Go语言示例包mainfuncmain(){i:=7758i++}我们先把C语言反汇编一下,看main部分,可以看到自增过程,如下:$gcc-oplusplustestc-gplusplus.c$objdump-Splusplus......inti=7758;movl$0x1e4e,-0x4(%rbp)#将7758赋给rbp寄存器i++;addl$0x1,-0x4(%rbp)#rbp寄存器加1...然后看Go反汇编,发现一个奇怪的现象,为了做对比结果我也用objdump生成汇编语句,发现之前的7758是直接被自增的7759覆盖,它们之间没有计算过程。$gobuild--gcflags="-l-N"-oplusplustestgoplusplus.go$objdump-Splusplustestgo......i:=7758movq$0x1e4e,(%rsp)#Assign7758torspregisteri++movq$0x1e4f,(%rsp)#将7759赋值给rsp寄存器...这是因为Go编译器对它进行了优化。我们看到的Plan9程序集是在编译的最后阶段生成的。同时,编译器做了很多优化,省去了很多无用代码(死代码)。例如,上面的代码经过了Go编译器SSA(StaticSingleAssignment)的优化。Go语言编译器将.go文件编译成机器码的过程中,会生成几十个版本的中间代码,中间伴随着代码优化,不会用到的片段会被删除,这个过程上述程序中将7758增加到7759将被编译器“优化”。”,只保留将7759覆盖到寄存器的过程。对于中间代码,我们可以使用GOSSAFUNC环境变量构建中间代码从源代码到机器码的数十次迭代。这种方法最终会生成ssa.html文件,方便用户查看,方法如下:这里还是用原来的Go文件example.packagemainfuncmain(){i:=7758i++}接下来进入同级目录文件,这里可能需要切换到root权限,执行命令#命令如下#GOSSAFUNC=gobuild<.gofile>#实际执行$GOSSAFUNC=maingobuildplusplus.go#runtimedumpedSSAto/usr/local/go-1.14/src/runtime/ssa.html#command-line-argumentsdumpedSSAto./ssa.html此时中间代码已经生成在ssa.html文件中,我们打开用浏览器查看,可以点击红框中的字体查看每一步中间代码的生成情况,也可以点击任意一行代码查看中间代码的转换关系。上面两张图中间有一长串中间代码,这里就不贴了。这里的浅色字体代表的是编译器“优化”过的代码,也就是死代码,不会被编译成最终的机器码。有细心的同学可能会发现这里最终编译出来的机器码genssa在我上面贴的代码中并没有赋值寄存器的操作,为什么,为什么会造成不一致呢?这就是-gcflags="-l-N"的作用。上面生成程序集的时候加入这个参数是为了防止内联(-l)和编译优化(-N),所以我们可以看到寄存器赋值的语句。同样的,我们也可以在生成SSA的时候加上这个参数,这样一些中间代码就不会被优化,我们可以看到对应的中间代码,如下。$GOSSAFUNC=maingobuild--gcflags="-l-N"plusplus.go#runtimedumpedSSAto/usr/local/go-1.14/src/runtime/ssa.html#command-line-argumentsdumpedSSAto./ssa.html此时再次查看生成的ssa.html,是禁止内联编译优化机器码的生成步骤。感兴趣的同学可以自行尝试。延伸阅读《走进Golang之编译器原理》链接《Go语言设计与实现》2.4中间代码生成https://draveness.me/golang/