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