【问题标题】:Pointers (GoLang)指针(GoLang)
【发布时间】:2021-12-16 08:54:17
【问题描述】:

这是一个菜鸟问题,请多多包涵。所以问题是为什么 f() 函数指向不同的地址。我的理解是变量 v 必须覆盖旧值。

package main
import "fmt"

var p = f()

func f() *int {
    v := 1
    return &v
}

func main() {
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(p)
}

//0xc0000140b0
//0xc0000140b8
//0xc0000140e0
//0xc000014098

【问题讨论】:

  • "我的理解是变量 v 必须覆盖旧值。"语言规范(值得一读!)在这种情况下不做任何保证。不保证地址保持不变,也不能保证必须更改。甚至不同的编译器也可能做不同的事情。
  • 与答案无关,但我对正确的英语表达 bear with me 表示赞赏。如此多的人(尤其是美国人)写“bare with me”,这具有相当……不同的含义。 :-) 同时,另一个小的英语点:你写“怀疑”的地方,你的意思是“问题”。这些之间有一个微妙的区别:doubt 具有怀疑的含义,而 question 没有:doubt 具有暗示的“可能甚至可能false”内置在其中。

标签: go pointers


【解决方案1】:

编译器检测到vescapes函数f,所以分配在堆上。每次调用 f 都会返回一个新的 v 实例,这就是每次调用都会看到不同地址的原因。

【讨论】:

  • 这确实是 OP 看到他现在看到的结果的原因。然而,由于v 的每个实例在下一次调用时都已“死”,因此语言规范允许编译器重新使用旧地址。这个编译器根本不够聪明。
  • 如果 GC 在两次调用之间运行,它就有机会释放变量。在这种情况下,它没有机会运行。 @torek,编译器真的会这样做吗,不知道是否还有其他对该函数的调用?
  • 一个无所不知(或至少足够聪明)的编译器会知道 fmt.Println 不允许保存指针,因此当值从 f 转义到 main 时,它确实不会从 那里 逃逸,因此分配本身可以向上移动到 main 本身的堆栈帧。如果f 被内联并且fmt.Println 被特例化为“纯”,那么简单的逃逸分析就足够了,我们根本不需要运行GC。
  • (注意:我在这里误用了pure的通常编译器特定的含义:纯函数是没有副作用的,输出是一种副作用,所以打印是本质上是不纯的。但我认为意思很清楚。)在编译时让编译器分析 fmt.Println 是“纯”的,因为它调用的所有内容同样是“纯”的,通常是有用的,然后会启用这种类型优化。
【解决方案2】:

给出一个简单的答案

Go 会寻找比当前栈帧存活时间更长的变量,然后 然后堆分配它们

基本上,变量 v 会转义函数 f 堆栈帧并在堆中分配,这就是为什么您每次都会看到不同的地址打印。

阅读这篇关于逃逸分析的精彩介绍。 https://medium.com/a-journey-with-go/go-introduction-to-the-escape-analysis-f7610174e890

尝试运行转义分析以查看所有被转义的变量。

go build -gcflags="-m" main.go:
./main.go:7:2: moved to heap: v   //points to v := 1
./main.go:12:15: moved to heap: v //points to fmt.Println(f())
./main.go:13:15: moved to heap: v //points to fmt.Println(f())
./main.go:14:15: moved to heap: v //points to fmt.Println(f())

请注意,最后一个 fmt.Println(f()) 语句不考虑转义,因为传递给 Println 的值是 p,它是一个全局变量,因此它已经在堆中,因此不需要 escape

【讨论】:

  • 谢谢@anuraag_100 作为一个小人物这对我帮助很大
猜你喜欢
  • 1970-01-01
  • 2015-01-10
  • 1970-01-01
  • 1970-01-01
  • 2017-06-21
  • 2019-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多