Go语言中数组的内存布局是怎样的?

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

Memory layout of arrays in Go?

问题

我的代码如下。当我检查值的地址时,它以280 288 290 298的模式存储。为什么会以这种模式存储?

package main
    
import "fmt"

func main() {
	const a = 1111111111111111111
	test := [7]int{a, 1, 33333,4,6,7,7}

	fmt.Println(&test[0])
	fmt.Println(&test[1])
	fmt.Println(&test[2])
	fmt.Println(&test[3])
	fmt.Println(&test[4])
	fmt.Println(&test[5])
	fmt.Println(&test[6])
}

输出结果:

0xc00000e280
0xc00000e288
0xc00000e290
0xc00000e298
0xc00000e2a0
0xc00000e2a8
0xc00000e2b0

这种存储模式是由于数组中的元素在内存中是连续存储的。在这种情况下,test[0]的地址是0xc00000e280test[1]的地址是0xc00000e288,以此类推。每个元素的地址相对于前一个元素的地址增加了8个字节,这是因为int类型在大多数系统上占据8个字节的空间。

英文:

My code is below. When I check the address of the values it is stored as 280 288 290 298. Why is it stored in this pattern?

package main
    
import "fmt"

func main() {
	const a = 1111111111111111111
	test := [7]int{a, 1, 33333,4,6,7,7}

	fmt.Println(&test[0])
	fmt.Println(&test[1])
	fmt.Println(&test[2])
	fmt.Println(&test[3])
	fmt.Println(&test[4])
	fmt.Println(&test[5])
	fmt.Println(&test[6])
}

output :

0xc00000e280
0xc00000e288
0xc00000e290
0xc00000e298
0xc00000e2a0
0xc00000e2a8
0xc00000e2b0

答案1

得分: 5

不出所料,Go数组在内存中是连续排列的。由于Go类型是静态大小的,第n个元素的地址等于第0个元素的地址加上一个字节偏移量,该偏移量等于元素类型的大小。

这可以用以下伪代码粗略地表示:

addr_n = addr_0 + (n * size_of(item))

在Go代码中,可以使用unsafe.Add(自Go 1.17起):

func main() {
	const a = 1111111111111111111
	x := [7]int{a, 1, 33333, 4, 6, 7, 7}

	unsafePointer := unsafe.Pointer(&x[0])
	for i := range x {
        step := unsafe.Sizeof(int(0))
		addr_n := unsafe.Add(unsafePointer, int(step)*i)
		fmt.Printf("addr: %p, val: %d\n", addr_n, *(*int)(addr_n))
	}
}

输出结果为:

addr: 0xc000102000, val: 1111111111111111111
addr: 0xc000102008, val: 1
addr: 0xc000102010, val: 33333
addr: 0xc000102018, val: 4
addr: 0xc000102020, val: 6
addr: 0xc000102028, val: 7
addr: 0xc000102030, val: 7

如果还不清楚的话,十六进制数是内存地址。这是fmt包通常格式化指针的方式。

但请注意,特别是int的大小是依赖于平台的,因此在上面的代码片段中不能简单地添加8。为了使其确定性,可以使用unsafe.Sizeof(int(0))

Playground: https://go.dev/play/p/4hu8efVed96

英文:

Unsurprisingly, Go arrays are laid out contiguously in memory. Then since Go types are statically sized, the address of the nth item is equal to the address of the 0th element plus a byte offset equal to the size of the type of the item.

This can be roughly formalized as (pseudo code):

addr_n = addr_0 + (n * size_of(item))

And in Go code, using unsafe.Add (since Go 1.17):

func main() {
	const a = 1111111111111111111
	x := [7]int{a, 1, 33333, 4, 6, 7, 7}

	unsafePointer := unsafe.Pointer(&x[0])
	for i := range x {
        step := unsafe.Sizeof(int(0))
		addr_n := unsafe.Add(unsafePointer, int(step)*i)
		fmt.Printf("addr: %p, val: %d\n", addr_n, *(*int)(addr_n))
	}
}

Which prints:

addr: 0xc000102000, val: 1111111111111111111
addr: 0xc000102008, val: 1
addr: 0xc000102010, val: 33333
addr: 0xc000102018, val: 4
addr: 0xc000102020, val: 6
addr: 0xc000102028, val: 7
addr: 0xc000102030, val: 7

<sup>In case it wasn't already crystal clear, the hex number is the memory address. This is how pointers are usually formatted by the fmt package.</sup>

However note that the size of int in particular is platform-dependent, hence in the snippet above you can't just add 8. To make it deterministic, you can use unsafe.Sizeof(int(0)).

Playground: https://go.dev/play/p/4hu8efVed96

huangapple
  • 本文由 发表于 2022年7月3日 16:14:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/72844907.html
匿名

发表评论

匿名网友

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

确定