Go1.20 将会修改全局变量的初始化顺序。梅度二开,继续打破 Go1 兼容性承诺!...

本文主要是介绍Go1.20 将会修改全局变量的初始化顺序。梅度二开,继续打破 Go1 兼容性承诺!...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家好,我是煎鱼。

Go1.20 已经发布了 rc1,大家都关注了一些大头的功能特性,例如:PGO、Arean 等,都没有那么的常接触到。

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

神奇案例

从案例展开,假设在同一个 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[2]》说了算。

0e2018cce5259967dc33fc34819a5b33.png
sepc

在规范中的包初始化(Package initialization)章节中明确指出:"在一个包中,包级别的变量初始化是逐步进行的,每一步都会选择声明顺序中最早的变量,它不依赖于未初始化的变量。"

更完整和准确的阐述:

  • 如果包级变量尚未初始化并且没有初始化表达式或其初始化表达式不依赖于未初始化的变量,则认为包级变量已准备好进行初始化。

  • 初始化通过重复初始化声明顺序中最早并准备初始化的下一个包级变量来进行,直到没有变量准备好进行初始化。

在了解了理论知识后,我们再结合官方例子看看,加强实践的补全。

例子 1。代码如下:

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

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

例子 2。代码如下:

var (a = c + b  // == 9b = f()    // == 4c = f()    // == 5d = 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

预期结果就一致了。

上一个案例输出 1 2 3,这是有 BUG!与 Go 规范定义的不一致。

修复时间

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

不过由于不知道是否会影响用户,因此 Go 官方将会更多的关注社区反馈。

当然,这个确实是 BUG,会修。也为此认为值得打破 Go1 兼容性的原则。

总结

今天这篇文章我们介绍了 Go 一直以来存在的一个 Go 编译/运行时的 BUG,会导致 Go 程序的全局变量会与 Go 规范本身定义的不一致,将预计会在 Go1.20 修复。

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

推荐阅读
  • Go1.20 中两个关于 Time 的更新,终于不用背 2006-01-02 15:04:05 了!

  • 打脸了兄弟们,Go1.20 arena 来了!

  • Go 十年了,终于想起要统一 log 库了!

参考资料

[1]

cmd/compile: global variable initialization done in unexpected order: https://github.com/golang/go/issues/51913

[2]

The Go Programming Language Specification: https://go.dev/ref/spec#Package_initialization

关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群👇

dac9895d0efe1023a6273df9c067e582.jpeg

11402ad862fc7d50eb40c87dc06b8155.png

你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

这篇关于Go1.20 将会修改全局变量的初始化顺序。梅度二开,继续打破 Go1 兼容性承诺!...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/312552

相关文章

python修改字符串值的三种方法

《python修改字符串值的三种方法》本文主要介绍了python修改字符串值的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录第一种方法:第二种方法:第三种方法:在python中,字符串对象是不可变类型,所以我们没办法直接

Mysql8.0修改配置文件my.ini的坑及解决

《Mysql8.0修改配置文件my.ini的坑及解决》使用记事本直接编辑my.ini文件保存后,可能会导致MySQL无法启动,因为MySQL会以ANSI编码读取该文件,解决方法是使用Notepad++... 目录Myhttp://www.chinasem.cnsql8.0修改配置文件my.ini的坑出现的问题

JAVA利用顺序表实现“杨辉三角”的思路及代码示例

《JAVA利用顺序表实现“杨辉三角”的思路及代码示例》杨辉三角形是中国古代数学的杰出研究成果之一,是我国北宋数学家贾宪于1050年首先发现并使用的,:本文主要介绍JAVA利用顺序表实现杨辉三角的思... 目录一:“杨辉三角”题目链接二:题解代码:三:题解思路:总结一:“杨辉三角”题目链接题目链接:点击这里

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

如何在运行时修改serialVersionUID

优质博文:IT-BLOG-CN 问题 我正在使用第三方库连接到外部系统,一切运行正常,但突然出现序列化错误 java.io.InvalidClassException: com.essbase.api.base.EssException; local class incompatible: stream classdesc serialVersionUID = 90314637791991