0 length slices and arrays in golang

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

0 length slices and arrays in golang

问题

我正在尝试弄清楚在Golang中0长度的数组和切片的行为。我找到了两段代码(我在某个地方找到了这些代码,并稍作修改以适应这个问题)。

链接1:https://play.golang.org/p/ew2YYgvpGC

链接2:https://play.golang.org/p/jm2p6L6WCG

我从网站上了解到,nil数组([]int(nil))的指针值为nil,所以我决定进行测试。果然如此。但是,make和切片数组的行为让我感到困惑。

这两段代码的行为真的让我很困惑。第一段在我的电脑和playground上都可以正常运行。我注意到第一个和最后一个数组的地址总是完全相同?为什么会这样?

为什么会这样?

第二段代码更奇怪。这与前一段代码完全相同,只是在其中插入了一些关于len/cap的代码片段。它在go playground上无法运行,最后一个切片数组出现了错误,由于某种原因,切片的长度变为3(在我的电脑上,最后一个切片的长度为0,而所有切片的容量为272851504)。但是它在我的电脑上可以运行。我注意到使用make创建的第一个数组的地址总是小于最后一个数组的地址。它们总是不同的,并且稍微小一些(第一个数组的地址),为什么?代码中没有更改数组的地址。

另外,为什么make()会创建一个数组?长度为0的数组在内存中是什么样子的?

英文:

Was trying to figure out how 0 length arrays and slices behaved in Golang. Came up with two snippets of code (I found the code somewhere and modified it a bit to work with this)

https://play.golang.org/p/ew2YYgvpGC

https://play.golang.org/p/jm2p6L6WCG

I learnt from the website that nil arrays ([]int(nil)) have a pointer value of nil, so I decided to test it. Sure enough, that is the case. I'm just confused on make and slicing an array. It has unexpected behaviour for me.

I am really confused by the behaviour of these two. The first one runs fine on my computer and on the playground. I've noticed that the address of the first and last array is always the exact same? Why?

Why is this?

The second one is weirder. This is the exact same code as the previous one, except there are snippets of other code for the len/cap in between. It does not run on the go playground, there is an error at the last one with the sliced array, for some reason, the slice gets a length of 3 (on my computer it is 0 for the last one, and the cap for all of them is 272851504). It does run on my computer however. I noticed that the address of the first array created with make is always smaller than the last one. Its always different, and a bit smaller (the first one), why? There is no change in the code for the address of the array

Also, why does make() even create an array? How does an array of length 0 even look in memory?

答案1

得分: 3

我注意到第一个和最后一个数组的地址总是完全相同的。为什么会这样?

这是因为你的函数中基于函数参数中的切片头部来确定地址。而恰好这个函数每次运行时都发生在相同的内存地址上。此外,作为实现细节,当你将原始切片缩减为零长度和容量时,它并不会将数据指针清零。在你的两个函数中,切片头部被复制,所以你甚至没有检查原始头部切片。

在第二个示例中,你没有正确读取切片头部。如果你想进行尝试,你不需要尝试进行指针运算,你可以直接获取切片的地址:

hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))

不过,如果你真的想通过指针运算来检查切片头部,也许这个示例可以更好地说明你需要做什么:https://play.golang.org/p/GL6NtyPNs8

func InspectSlice(slice *[]int) {
    // 通过将其转换为 reflect.SliceHeader 直接获取头部
    hdr := (*reflect.SliceHeader)(unsafe.Pointer(slice))

    // 通过指针操作创建一个用于比较的头部
    address := (uintptr)(unsafe.Pointer(slice))
    lenAddr := address + unsafe.Sizeof(address)
    capAddr := lenAddr + unsafe.Sizeof(int(0))

    unsafeHdr := reflect.SliceHeader{
        Data: *(*uintptr)(unsafe.Pointer(address)),
        Len:  *(*int)(unsafe.Pointer(lenAddr)),
        Cap:  *(*int)(unsafe.Pointer(capAddr)),
    }

    fmt.Printf("Real Header:  %#v\n", *hdr)
    fmt.Printf("UnsafeHeader: %#v\n", unsafeHdr)
}

另外,为什么 make() 会创建一个数组?

make() 并不会创建一个数组,而你标记为 array 的实际上是一个切片字面量。

一个长度为0的数组在内存中是什么样子?

它不会占用任何空间。它消耗0字节。

当创建一个零长度的切片时,你看到的很可能是一个指向全局零值数组的指针。创建多个不同类型的零长度切片,它们的数据指针都将是相同的。就像所有空的 struct{} 和空的 nil interface{} 都被赋予相同的值作为优化一样。这完全是一个实现细节,因为你不应该访问那个数据值。当将切片切回零长度和容量时,运行时也不会将数据指针清零。由于这两个行为,任何切片的数据指针很少会是 nil。

英文:

> I've noticed that the address of the first and last array is always
> the exact same? Why?
>
> Why is this?

You are basing the addresses in your function on the slice header in the function arguments. And by chance that function happens to run at the same memory address each time. Also, as an implementation detail, when you slice the original down to zero length and capacity, it doesn't zero out the Data pointer. In both your functions, the slice header is copied, so you're not even examining the original header slice to begin with.

The second example, you're not correctly reading out the slice header.
If you want to play around, you don't need to try and do pointer arithmetic, and you you can take the address of the slice directly

hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))

Though, if you really want to inspect a slice header via pointer arithmetic, maybe this can better clarify what you need to do: https://play.golang.org/p/GL6NtyPNs8

func InspectSlice(slice *[]int) {
	// Get the header directly by converting it to a reflect.SliceHeader
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(slice))

	// Create a header for comparison via pointer manipulation
	address := (uintptr)(unsafe.Pointer(slice))
	lenAddr := address + unsafe.Sizeof(address)
	capAddr := lenAddr + unsafe.Sizeof(int(0))

	unsafeHdr := reflect.SliceHeader{
		Data: *(*uintptr)(unsafe.Pointer(address)),
		Len:  *(*int)(unsafe.Pointer(lenAddr)),
		Cap:  *(*int)(unsafe.Pointer(capAddr)),
	}

	fmt.Printf("Real Header:  %#v\n", *hdr)
	fmt.Printf("UnsafeHeader: %#v\n", unsafeHdr)

}

> Also, why does make() even create an array?

Make doesn't create an array, and what you have labeled as array is just a slice literal.

> How does an array of length 0 even look in memory?

It doesn't look like anything. It consumes 0 bytes.

What you are seeing when making a zero-length slice is most likely a pointer to a global zero value array. Make a bunch zero length slices of different types, and the Data pointers will all be the same. Just as all empty struct{} and empty nil interface{} are given the same value as an optimization. This is entirely an implementation detail, as you are not expected to access that Data value. When slicing a slice back to zero length and capacity, the runtime also doesn't zero out the Data pointer. Because of these two behaviors, the Data pointer in any slice is rarely going to be nil.

huangapple
  • 本文由 发表于 2015年3月24日 01:13:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/29216252.html
匿名

发表评论

匿名网友

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

确定