指针(GoLang)
Posted
技术标签:
【中文标题】指针(GoLang)【英文标题】:Pointers (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”内置在其中。 【参考方案1】:编译器检测到v
escapes
函数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 作为一个小人物这对我帮助很大以上是关于指针(GoLang)的主要内容,如果未能解决你的问题,请参考以下文章