乐趣区

关于后端:Go120-将会修改全局变量的初始化顺序梅开二度继续打破-Go1-兼容性承诺

大家好,我是煎鱼。

Go1.20 公布在即,大家都关注了一些大头的性能个性,例如:PGO、Arean 等。都没有那么的常接触到。

本质上本次新版本还修复了在全局变量初始化方面的程序,来自《cmd/compile: global variable initialization done in unexpected order》,这是个挺乏味的问题。

神奇案例

从案例开展,假如在同一个 package 下有 2 个文件,别离是:f1.go 和 f2.go,蕴含了不同的包全局变量申明和代码。

文件 f1.go。代码如下:

package main    
   
var A int = 3    
var B int = A + 1    
var C int = A

文件 f2.go。代码如下:

package main    
   
import "fmt"    
                     
var D = f()      
   
func f() int {    
  A = 1    
  return 1    
}    
   
func main() {fmt.Println(A, B, C)    
}  

问题来了。

如果运行 go run f1.go f2.go,会输入什么后果?

运行后果如下:

1 4 3

你答对了吗?再认真想想。

如果运行 go run f2.go f1.go,会输入什么后果?

运行后果如下:

1 2 3

这只是 run 的文件先后顺序不一样了,咋就连输入的后果都不一样了?

输入后果到底谁对谁错,还是说都错了,正确的是什么?

Go 标准定义

咱们要晓得正确输入的后果是什么,还得是看 Go 语言标准《The Go Programming Language Specification》说了算。

在标准中的包初始化(Package initialization)章节中明确指出:” 在一个包中,包级别的变量初始化是逐渐进行的,每一步都会抉择申明程序中最早的变量,它不依赖于未初始化的变量 。”

更残缺和精确的论述:

  • 如果包级变量尚未初始化并且没有初始化表达式或其初始化表达式不依赖于未初始化的变量,则认为包级变量已筹备好进行初始化。
  • 初始化通过反复初始化申明程序中最早并筹备初始化的下一个包级变量来进行,直到没有变量筹备好进行初始化。

在理解了理论知识后,咱们再联合官网例子看看,增强实际的补全。

例子 1。代码如下:

var x = a
var a, b = f()

在初始化变量 x 之前,变量 a 和 b 会一起初始化(在同一步骤中)。

例子 2。代码如下:

var (
    a = c + b  // == 9
    b = f()    // == 4
    c = f()    // == 5
    d = 3      // == 5 after initialization has finished
)

func f() int {
    d++
    return d
}

初始化程序是:d, b, c, a。

案例哪里有问题

在解读了背景和标准后,再次回顾文章刚开始的案例。

文件 f1.go。代码如下:

package main    
   
var A int = 3    
var B int = A + 1    
var C int = A

文件 f2.go。代码如下:

package main    
   
import "fmt"    
                     
var D = f()      
   
func f() int {    
  A = 1    
  return 1    
}    
   
func main() {fmt.Println(A, B, C)    
}  

第一种,运行 go run f1.go f2.go,输入:1 4 3。

第二种,运行 go run f2.go f1.go,输入:1 2 3.

如果依照标准来,分析程序变量初始化程序和应该输入的后果。如下:

  • A < B < C < D:产生在你编译我的项目时,运行命令先把 f1.go 传给编译器,而后再传 f2.go。在这种状况下,输入后果是 1 4 3。
  • A < D < B < C:产生在先将 f2.go 传给编译器时。在这种状况下,预期输入是 1 2 1。然而,理论的输入是 1 2 3。

问题出在第二种状况,咱们尝试改一下写法,变成如下代码:

package main    
   
import "fmt"    
   
var A int = initA()    
var B int = initB()    
var C int = initC()    
     
func initA() int {fmt.Println("Init A")    
  return 3    
}    
     
func initB() int {fmt.Println("Init B")    
  return A + 1    
}    
 
func initC() int {fmt.Println("Init C")    
  return A    
} 

输入后果:

Init A
Init B
Init C
1 2 1

预期后果就统一了。

这是有 BUG!与 Go 标准定义的不统一。

修复工夫

目前这个问题曾经明确是 Go 编译 / 运行时的 BUG,并且这个问题曾经存在了很久,将打算在 Go1.20 中修复。

不过因为不晓得是否会影响用户,因而 Go 官网将会更多的关注社区反馈。当然,这个的确是 BUG,会修。也为此认为值得突破 Go1 兼容性的准则。

总结

明天这篇文章咱们介绍了 Go 始终以来存在的一个 Go 编译 / 运行时的 BUG,会导致 Go 程序的全局变量会与 Go 标准自身定义的不统一,将预计会在 Go1.20 修复。

这也是 Go 突破 Go1 兼容性承诺的一个案例。值得咱们关注。

文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。

Go 图书系列

  • Go 语言入门系列:初探 Go 我的项目实战
  • Go 语言编程之旅:深刻用 Go 做我的项目
  • Go 语言设计哲学:理解 Go 的为什么和设计思考
  • Go 语言进阶之旅:进一步深刻 Go 源码

举荐浏览

  • Go1.20 中两个对于 Time 的更新,终于不必背 2006-01-02 15:04:05 了!
  • 打脸了兄弟们,Go1.20 arena 来了!
  • Go 十年了,终于想起要对立 log 库了!
退出移动版