在Go语言中进行切片类型转换。

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

Slice type casting in Go

问题

我对Go语言还比较陌生,来自C++背景,遇到了一些奇怪的问题。以下是要翻译的代码:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    arr := []string{"one", "two", "three"}
    address := unsafe.Pointer(&arr)
    addPtr := (*[]string)(unsafe.Pointer(*(*uintptr)(address)))
    fmt.Println((*addPtr)[0])
}

这段代码报错如下:

runtime error: growslice: len out of range

如果我将类型转换更改为以下形式:

addPtr := (*[0]string)(unsafe.Pointer(*(*uintptr)(address)))

上述代码将正常工作。

我理解这是将数组指针进行类型转换,而数组必须具有常量大小,但是如何将其转换为切片指针呢?

更令人困惑的是,可以获取切片的地址并将其分配给指针,如下所示:

func main() {
    arr := []string{"one", "two", "three"}
    var arrPtr *[]string = &arr
    fmt.Println((*arrPtr)[0])
}

这次,尽管指针的类型与我在第一个示例中进行不安全指针转换的类型相同,但一切都能正常工作。有人可以帮助理解这里到底发生了什么吗?

英文:

I'm quite new to Go coming from C++ background and have stumbled upon some weird issue.
Here's the code:

package main

import (
"fmt"
"unsafe"
)

func main() {
     arr := []string { "one", "two", "three" }
     address := unsafe.Pointer(&arr)
     addPtr := (*[]string)(unsafe.Pointer(*(*uintptr)(address)))
     fmt.Println((*addPtr)[0])
}

This code fails with:

> runtime error: growslice: len out of range

If i change cast, for example, to this:

addPtr := (*[0]string)(unsafe.Pointer(*(*uintptr)(address)))

The code above works just fine.

I understand that this is a cast to an array pointer and array must have constant size,
but how to cast it to pointer to a slice then?

What's even more confusing that it's possible to take an address of slice and assign it to the pointer like this:

func main() {
     arr := []string { "one", "two", "three" }
     var arrPtr *[]string = &arr
     fmt.Println((*arrPtr)[0])
}

And this time everything will work despite the fact that the type of pointer is the same type that i was casting unsafe pointer to in the first example.
Can someone help to understand what's exactly going on here?

答案1

得分: 2

一些背景信息:切片头包含指向后备数组的指针、长度和容量。

问题的第一部分代码将切片头转换为切片头指针。go vet 命令警告该问题中的代码可能误用了 unsafe.Pointer。

修复方法是删除额外的解引用操作,使代码从切片头指针转换为切片头指针。

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[]string)(unsafe.Pointer((*uintptr)(address)))
fmt.Println((*addPtr)[0]) // 输出 one

不需要将其转换为 *uintptr。简化为:

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[]string)(unsafe.Pointer(address))
fmt.Println((*addPtr)[0]) // 输出 one

不需要使用 unsafe 操作。简化为:

arr := []string{"one", "two", "three"}
addPtr := &arr
fmt.Println((*addPtr)[0]) // 输出 one

使用以下代码将切片的后备数组指针转换为数组指针。该代码很脆弱,因为它假设切片头的第一个字是指向后备数组的指针。

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[1]string)(unsafe.Pointer(*(*uintptr)(address)))
fmt.Println((*addPtr)[0]) // 输出 one

前面片段中的 uintptr 转换是不必要的。简化为:

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[]string)(address)
fmt.Println((*addPtr)[0]) // 输出 one

希望对你有所帮助。

英文:

Some background: A slice header contains a pointer to a backing array, length and capacity.

The code in the first part of the question converts a slice header to a pointer to a slice header. The go vet command warns that the code in the question is possible misuse of unsafe.Pointer.

Fix by removing the extra dereference operation so that the code converts from pointer to slice header to pointer to slice header.

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[]string)(unsafe.Pointer((*uintptr)(address)))
fmt.Println((*addPtr)[0]) // prints one

The conversion to *uintptr is not required. Simplify to:

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[]string)(unsafe.Pointer(address))
fmt.Println((*addPtr)[0]) // prints one

The unsafe shenanigans are not required. Simplify to:

arr := []string{"one", "two", "three"}
addPtr := &arr
fmt.Println((*addPtr)[0]) // prints one

Convert the slice's backing array pointer to an array pointer using the following code. The code is fragile because it assumes that the first word of the slice header is the pointer to the backing array.

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[1]string)(unsafe.Pointer(*(*uintptr)(address)))
fmt.Println((*addPtr)[0]) // prints one

The uintptr conversions in the previous snippet are not needed. Simplify to:

arr := []string{"one", "two", "three"}
address := unsafe.Pointer(&arr)
addPtr := (*[]string)(address)
fmt.Println((*addPtr)[0]) // prints one

I hope this helps.

huangapple
  • 本文由 发表于 2023年6月25日 08:40:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76548471.html
匿名

发表评论

匿名网友

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

确定