英文:
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 = 600
,nm
的输出将显示:
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 <--- 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论