全局变量的地址

huangapple go评论87阅读模式
英文:

Address of 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语言中的全局变量的概念上漏掉了一点。

英文:

I was just playing around with Go interfaces and structs that suddenly found something weird to me. This is the case:

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

output:

0xc0000ba010
0x57b2a8

The value of the addresses doesn't matter. The point is that Why is the number of digits of first address more than second?

I think I have missed a point about the concept of global variables in Go.

答案1

得分: 10

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

scopedInt 可能会在堆上分配,因为它“逃逸”了 main() 函数(它的地址被传递给了 fmt.Printf()),而 globalInt 是一个包级变量,因此将在固定大小的段之一中分配,即 数据段

只要地址指向有效的内存区域,地址的“长度”并不重要。Go 语言具有自动内存管理,所以除非你使用 unsafe 包,否则不必担心地址和指针是否有效。

要了解更多关于内存管理的信息,请参阅 Doug Richardson: Go Memory Management。其中引用了以下内容:

> #### 什么放在哪里?
>
> Go 编程语言规范没有定义项将在哪里分配。例如,定义为 var x int 的变量可以分配在堆栈或堆上,并且仍然符合语言规范。同样,p := new(int)p 指向的整数可以分配在堆栈或堆上。
>
> 然而,某些要求会在特定条件下排除某些内存选择。例如:
>
> - 数据段的大小不能在运行时更改,因此不能用于大小可变的数据结构。
> - 栈中项的生命周期按照它们在栈上的位置进行排序。如果栈的顶部地址为 X,则 X 以上的所有内容将被释放,而 X 以下的内容将保持分配。由函数分配的内存如果被函数范围之外的项引用,则会逃逸出该函数,因此不能在栈上分配(因为它仍然被引用),也不能在数据段中分配(因为数据段不能在运行时增长),因此必须在堆上分配 - 尽管内联可以消除其中一些堆分配。

英文:

The "length" of the addresses differs by many digits because those variables are allocated on different areas of the memory, which have different offsets (starting locations).

scopedInt will likely be allocated on the heap because it "escapes" from main() (its address is passed to fmt.Printf()), while globalInt is a package level variable and as such will be allocated in one of the fixed sized segments, the data segment.

The "length" of addresses doesn't matter as long as they point to a valid memory area. Go has automatic memory management, so unless you touch package unsafe, you don't have to worry about addresses and pointers being valid or not.

To read more about memory management, see Doug Richardson: Go Memory Management. Quoting from it:

> #### What Goes Where?
>
> The Go Programming Language Specification does not define where items will be allocated. For example, a variable defined as var x int could be allocated on the stack or the heap and still follow the language spec. Likewise, the integer pointed to by p in p := new(int) could be allocated on the stack or the heap.
>
> However, certain requirements will exclude some choices of memory in certain conditions. For instance:
>
> - The size of the data segment cannot change at run time, and therefore cannot be used for data structures that change size.
> - The lifetime of items in the stack are ordered by their position on the stack. If the top of the stack is address X then everything above X will be deallocated while everything below X will remain allocated. Memory allocated by a function can escape that function if referenced by an item outside the scope of the function and therefore cannot be allocated on the stack (because it’s still being referenced), and neither can it be allocated in the data segment (because the data segment cannot grow at runtime), thus it must be allocated on the heap – although inlining can remove some of these heap allocations.

答案2

得分: 2

全局未初始化符号(如globalInt)存储在BSS段中,靠近数据段,在程序的较低地址空间中。

您可以使用nm实用程序检查程序符号的地址:

$ go build main.go
$ go tool nm main | grep globalInt

输出:

118e210 B main.globalInt

输出中的第一个十六进制数是符号的地址,在运行程序时将打印该地址。例如,在我的机器上:

$ ./main
0xc000124008
0x118e210      <--- 与nm输出相同

十六进制后面的字母B表示bss段符号

如果在源代码中显式初始化变量,例如var globalInt int = 600nm的输出将显示:

114b268 D main.globalInt

现在,D表示数据段符号

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

<hr>

请注意,所有这些都是实现相关的。规范没有规定对象的分配位置。

如果您使用TinyGo编译和运行相同的程序,输出将不会相同:

$ tinygo build -o tinymain main.go
$ ./tinymain 
0x1158bf040
0x1061f3ca8
英文:

Global uninitialized symbols like globalInt are stored in the BSS segment, close to the data segment, in the lower address space of the program.

You can check the addresses of program symbols with the nm utility:

$ go build main.go
$ go tool nm main | grep globalInt

Outputs:

118e210 B main.globalInt

The first hex in the output is the address of the symbol, which is what will be printed when you run the program. E.g. on my machine:

$ ./main
0xc000124008
0x118e210      &lt;--- same as nm output

The letter B after the hex stands for bss segment symbol.

If you explicitly initialize the variable in the source code, like var globalInt int = 600 the output of nm will show:

114b268 D main.globalInt

where now D stands for data segment symbol.

Anyway all these are in the lower address space of the program. The scopedInt doesn't pass escape analysis and is allocated on the heap, where its address will be higher than the global var.

<hr>

Consider that all this is implementation-dependent. The specs don't mandate where to allocate objects.

If you compile and run the same program with TinyGo, the output will not look alike:

$ tinygo build -o tinymain main.go
$ ./tinymain 
0x1158bf040
0x1061f3ca8

huangapple
  • 本文由 发表于 2021年7月2日 19:16:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/68224243.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定