全局变量地址

时间:2021-07-02 11:16:01

标签: go pointers global-variables

我只是在玩 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 中全局变量概念的一点。

2 个答案:

答案 0 :(得分:9)

地址的“长度”相差很多位,因为这些变量分配在内存的不同区域,它们具有不同的偏移量(起始位置)。

scopedInt 可能会在堆上分配,因为它从 main() 中“转义”(它的地址被传递给 fmt.Printf()),而 globalInt 是一个包级变量并且因此将分配在固定大小的段之一中,data segment

地址的“长度”并不重要,只要它们指向有效的内存区域即可。 Go 有自动内存管理,所以除非你接触包 unsafe,否则你不必担心地址和指针是否有效。

要阅读有关内存管理的更多信息,请参阅 Doug Richardson: Go Memory Management。引用它:

<块引用>

何去何从?

Go 编程语言规范没有定义项目的分配位置。例如,定义为 var x int 的变量可以在堆栈或堆上分配,并且仍然遵循语言规范。同样,p 中的 p := new(int) 指向的整数可以分配在堆栈或堆上。

但是,某些要求会在某些情况下排除某些内存选择。例如:

  • 数据段的大小不能在运行时改变,因此不能用于改变大小的数据结构。
  • 堆栈中项目的生命周期按它们在堆栈中的位置排序。如果堆栈的顶部是地址 X,则 X 之上的所有内容都将被释放,而 X 之下的所有内容将保持分配状态。由函数分配的内存如果被函数范围之外的项引用,则可以转义该函数,因此不能在堆栈上分配(因为它仍在被引用),也不能在数据段中分配(因为数据段不能在运行时增长),因此它必须在堆上分配——尽管内联可以删除其中一些堆分配。

答案 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 = 600nm 的输出将显示:

114b268 D main.globalInt

现在 D 代表 data segment symbol

无论如何,所有这些都在程序的低地址空间中。 scopedInt 未通过转义分析并分配在堆上,其地址将高于全局变量。


考虑到所有这些都依赖于实现。规范并未强制要求将对象分配到何处。

如果你用 TinyGo 编译和运行相同的程序,输出看起来会不一样:

$ tinygo build -o tinymain main.go
$ ./tinymain 
0x1158bf040
0x1061f3ca8
相关问题