变量初始化时是否出现错误?

5
作为 golang规范的一部分,我尝试编写测试代码。根据规范,初始化顺序应该是d、b、c、a,我认为b应该是4,c应该是5,但实际上我得到了b == 5且c == 4,这是哪里出了问题?还是我误解了规范?我在以下go版本中进行了尝试。
go version go1.12.4 linux/amd64

package main

import "fmt"

var (
    a = c + b
    b = f()
    c = f()
    d = 3
)

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

func main() {
    fmt.Println("a", a)
    fmt.Println("b", b)
    fmt.Println("c", c)
    fmt.Println("d", d)
}

结果是

result:
a 9
b 5
c 4
d 5

期望 b = 4,c = 5


这似乎是编译器的一个错误。顺序应该是 d,b,c,a,因为 bc 之前被声明。 - for_stack
1
此外,在我看来,编写那样的代码是个坏主意 :) - for_stack
你应该在 golang-nuts 或 golang-dev 上提问。 - Volker
5
cmd/compile: 在规范示例#22326中,包初始化顺序不正确:https://github.com/golang/go/issues/22326 - peterSO
https://github.com/golang/go/issues/22326在8天前已关闭,所以我猜下一个发布版本将修复该问题! - Vorsprung
你好,我已经在go 1.14.6编译器上尝试过了。我无法再现这种行为。游乐场链接:https://play.golang.org/p/e-fN3f9Al2W。 - Rishabh Bhatnagar
2个回答

0

不,正如你所说它执行了d,c,b,a

所以d是3

然后c调用f(),d变成4,因此c为4

接下来b调用f(),d变成5,因此b为5

最后,a等于c+b或9

d由于f()函数的副作用最终变成了5

这是相关部分的编译器输出

正如你所看到的,它先设置了c(c(SB)),然后是b,最后是a

  0x004b 00075 (b2.go:6)  CALL  "".f(SB)
  0x0050 00080 (b2.go:6)  MOVQ  (SP), AX
  0x0054 00084 (b2.go:6)  MOVQ  AX, "".c(SB)
  0x005b 00091 (b2.go:5)  CALL  "".f(SB)
  0x0060 00096 (b2.go:5)  MOVQ  (SP), AX
  0x0064 00100 (b2.go:5)  MOVQ  AX, "".b(SB)
  0x006b 00107 (b2.go:4)  MOVQ  "".c(SB), CX
  0x0072 00114 (b2.go:4)  ADDQ  CX, AX
  0x0075 00117 (b2.go:4)  MOVQ  AX, "".a(SB)

(为了生成此输出,我使用了go tool compile -S -N abcd.go > abcd.s。实际代码行号来自略微简化的版本,其中省略了fmt导入和print语句)

这是使用go版本go1.11.4 linux/amd64的结果。

尝试使用相同的go版本go1.12.4 linux/amd64,结果相同。


1
NO. bc之前被声明,因此应该在初始化c之前先初始化b - for_stack
2
文档(https://golang.org/ref/spec#Package_initialization)指出:“初始化顺序为d,b,c,a。”因此,d、c、b、a似乎是不正确的,或者文档有误。 - mkopriva
@Vorsprung,你是如何生成汇编转储的? - colm.anseo
1
@colminator在答案中添加了一条注释关于那个。 - Vorsprung

0

简短内容: - 是的,您的输出存在错误。这里是您期望的代码运行并输出的结果:https://play.golang.org/p/bNUISdsPKyU

一些背景知识。变量按顺序初始化为 d、b、c、a。如果一个变量引用了另一个未初始化的变量,Go会推迟初始化,直到它了解到未初始化的变量。

在变量初始化过程的每个步骤中询问: 这个变量是否准备好初始化?

怎么判断呢?

……如果包级别变量尚未初始化且没有初始化表达式,或其初始化表达式对未初始化的变量没有依赖关系,则将其视为可初始化的状态……依赖分析不依赖于变量的实际值,只依赖于源代码中对它们的词法引用,并进行传递性分析。

因此,如果有 var a = b,我们将不会初始化 a 直到我们初始化了 b

让我们来看一下示例代码中的第一个变量……

var (
    a = c + b
    // Is this var ready for initialisation? 
    // No. It depends on uninitialized variables `c` and `b`.
    // Go initialization will skip this var until it knows about its dependencies 

使用剩余的变量重复此过程,您将获得与Go编译器相同的结果。

步骤的概念总结:

1. Try initialise `a`. It depends on uninitialized `b` and `c`. Skip!
2. Try initialise `b`. It depends on uninitialized `d`. Skip!
3. Try initialise `c`. It depends on uninitialized `d`. Skip!
4. Try initialise `d`. Success!
5. Try initialise `a`. It depends on uninitialized `b` and `c`. Skip!
6. Try initialise `b`. Success!
7. Try initialise `c`. Success!
8. Try initialise `a`. Success!

请注意步骤的顺序,它们宣布了“成功!”:d、b、c、a

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接