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

相关文章

SQL Server修改数据库名及物理数据文件名操作步骤

《SQLServer修改数据库名及物理数据文件名操作步骤》在SQLServer中重命名数据库是一个常见的操作,但需要确保用户具有足够的权限来执行此操作,:本文主要介绍SQLServer修改数据... 目录一、背景介绍二、操作步骤2.1 设置为单用户模式(断开连接)2.2 修改数据库名称2.3 查找逻辑文件名

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

Oracle修改端口号之后无法启动的解决方案

《Oracle修改端口号之后无法启动的解决方案》Oracle数据库更改端口后出现监听器无法启动的问题确实较为常见,但并非必然发生,这一问题通常源于​​配置错误或环境冲突​​,而非端口修改本身,以下是系... 目录一、问题根源分析​​​二、保姆级解决方案​​​​步骤1:修正监听器配置文件 (listener.

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

Nginx 413修改上传文件大小限制的方法详解

《Nginx413修改上传文件大小限制的方法详解》在使用Nginx作为Web服务器时,有时会遇到客户端尝试上传大文件时返回​​413RequestEntityTooLarge​​... 目录1. 理解 ​​413 Request Entity Too Large​​ 错误2. 修改 Nginx 配置2.1

使用@Cacheable注解Redis时Redis宕机或其他原因连不上继续调用原方法的解决方案

《使用@Cacheable注解Redis时Redis宕机或其他原因连不上继续调用原方法的解决方案》在SpringBoot应用中,我们经常使用​​@Cacheable​​注解来缓存数据,以提高应用的性能... 目录@Cacheable注解Redis时,Redis宕机或其他原因连不上,继续调用原方法的解决方案1

Python对PDF书签进行添加,修改提取和删除操作

《Python对PDF书签进行添加,修改提取和删除操作》PDF书签是PDF文件中的导航工具,通常包含一个标题和一个跳转位置,本教程将详细介绍如何使用Python对PDF文件中的书签进行操作... 目录简介使用工具python 向 PDF 添加书签添加书签添加嵌套书签Python 修改 PDF 书签Pytho

MySQL中SQL的执行顺序详解

《MySQL中SQL的执行顺序详解》:本文主要介绍MySQL中SQL的执行顺序,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql中SQL的执行顺序SQL执行顺序MySQL的执行顺序SELECT语句定义SELECT语句执行顺序总结MySQL中SQL的执行顺序

C++类和对象之初始化列表的使用方式

《C++类和对象之初始化列表的使用方式》:本文主要介绍C++类和对象之初始化列表的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C++初始化列表详解:性能优化与正确实践什么是初始化列表?初始化列表的三大核心作用1. 性能优化:避免不必要的赋值操作2. 强