Bug

大家看看下面这段程序,思考下输出结果应该是什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func main() {
  n := 0

  f := func() func(int, int) {
    n = 1
    return func(int, int) {}
  }
  g := func() (int, int) {
    println(n)
    return 0, 0
  }

  f()(g())
}

先思考几秒钟。。。


在Go 1.13版本之前,上述程序打印的结果是

1
1

在Go 1.13版本开始,上述程序打印的结果是

1
0

从习惯认知来看,编译器应该按照如下顺序执行

  • step 1: evaluate f(),此时变量n的值为1
  • step 2: evaluate g(),此时打印n时,因为step 1已经把n修改为1了,所以应该打印1
  • step3: 根据step 1和step 2的结果,执行最终的函数调用

Go编译器其实也是遵循这个设计的,官方文档的描述如下:

At package level, initialization dependencies determine the evaluation order of individual initialization expressions in variable declarations.

Otherwise, when evaluating the operands of an expression, assignment, or return statement, all function calls, method calls, and communication operations are evaluated in lexical left-to-right order.

最后一句的表述可以知道,在执行函数调用时,对于函数的操作数,语法上是按照从左到右的顺序进行解析的。

这个bug其实是Google Go团队成员Matthew Dempsky提出来的,Go官方认领了这个bug,这个bug产生的原因也很明显,就是先执行了g(),然后执行的f()

This is because we rewrite f()(g()) into t1, t2 := g(); f()(t1, t2) during type checking.

gccgo compiles it correctly.

@cuonglm Yeah, rewriting as t0 := f(); t1, t2 := g(); t0(t1, t2) instead seems like a reasonable quick fix to me. I think direct function calls and method calls are probably the most common case though, so we should make sure those are still handled efficiently.

Longer term, I think we should probably incorporate order.go directly into unified IR.

目前这个bug的影响范围如下:

  • Go 1.13版本开始引入的这个bug,目前已经被修复,预计在1.19版本发布。
  • 只有官方的编译器gc有这个bug,如果你使用的是gccgo编译器,也不会有这个问题。
  • 只对多个参数的函数调用才会有这个bug,比如下面这个例子f()的结果是一个函数,该函数只有1个参数,就不会有本文提到的这个bug。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

func main() {
	n := 0

	f := func() func(int) {
		n = 1
		return func(int) {}
	}
	g := func() int {
		println(n)
		return 0
	}

	f()(g())
}

上面程序执行结果是1

推荐阅读

开源地址

文章和示例代码开源在GitHub: Go语言初级、中级和高级教程

公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。

个人网站:Jincheng’s Blog

知乎:无忌

References