我只是在玩 Go 接口和结构,突然发现一些奇怪的东西。情况是这样的:
https://play.golang.org/p/FgvRFV9Lij9
package main
import (
"fmt"
)
func main() {
scopedInt := 100
fmt.Printf("%p\n", &scopedInt)
globalInt = 100
fmt.Printf("%p\n", &globalInt)
}
var globalInt int
输出:
0xc0000ba010
0x57b2a8
地址的值无关紧要。重点是为什么第一个地址的位数比第二个多?
我想我错过了关于 Go 中全局变量概念的一点。
答案 0 :(得分:9)
地址的“长度”相差很多位,因为这些变量分配在内存的不同区域,它们具有不同的偏移量(起始位置)。
scopedInt
可能会在堆上分配,因为它从 main()
中“转义”(它的地址被传递给 fmt.Printf()
),而 globalInt
是一个包级变量并且因此将分配在固定大小的段之一中,data segment。
地址的“长度”并不重要,只要它们指向有效的内存区域即可。 Go 有自动内存管理,所以除非你接触包 unsafe
,否则你不必担心地址和指针是否有效。
要阅读有关内存管理的更多信息,请参阅 Doug Richardson: Go Memory Management。引用它:
<块引用>Go 编程语言规范没有定义项目的分配位置。例如,定义为 var x int
的变量可以在堆栈或堆上分配,并且仍然遵循语言规范。同样,p
中的 p := new(int)
指向的整数可以分配在堆栈或堆上。
但是,某些要求会在某些情况下排除某些内存选择。例如:
答案 1 :(得分:2)
全局未初始化符号如globalInt
存储在BSS段中,靠近数据段,在程序的低地址空间。
您可以使用 nm
utility 检查程序符号的地址:
$ go build main.go
$ go tool nm main | grep globalInt
输出:
118e210 B main.globalInt
输出中的第一个十六进制是符号的地址,它是您运行程序时将打印的内容。例如。在我的机器上:
$ ./main
0xc000124008
0x118e210 <--- same as nm output
十六进制后面的字母 B
代表 bss segment symbol
。
如果您在源代码中显式初始化变量,例如 var globalInt int = 600
,nm
的输出将显示:
114b268 D main.globalInt
现在 D
代表 data segment symbol
。
无论如何,所有这些都在程序的低地址空间中。 scopedInt
未通过转义分析并分配在堆上,其地址将高于全局变量。
考虑到所有这些都依赖于实现。规范并未强制要求将对象分配到何处。
如果你用 TinyGo 编译和运行相同的程序,输出看起来会不一样:
$ tinygo build -o tinymain main.go
$ ./tinymain
0x1158bf040
0x1061f3ca8