使用 reflect.StringHeader 将字节转换为字符串仍然分配新内存?

我有这个小代码片段来测试两种将字节切片转换为字符串对象的方法,一个分配新字符串对象的函数,另一个使用不安全指针算法构造字符串 *,它不分配新内存:


package main


import (

    "fmt"

    "reflect"

    "unsafe"

)


func byteToString(b []byte) string {

    return string(b)

}


func byteToStringNoAlloc(b []byte) string {

    if len(b) == 0 {

        return ""

    }

    sh := reflect.StringHeader{uintptr(unsafe.Pointer(&b[0])), len(b)}

    return *(*string)(unsafe.Pointer(&sh))

}


func main() {

    b := []byte("hello")

    fmt.Printf("1st element of slice: %v\n", &b[0])


    str := byteToString(b)

    sh := (*reflect.StringHeader)(unsafe.Pointer(&str))

    fmt.Printf("New alloc: %v\n", sh)


    toStr := byteToStringNoAlloc(b)

    shNoAlloc := (*reflect.StringHeader)(unsafe.Pointer(&toStr))

    fmt.Printf("No alloc: %v\n", shNoAlloc) // why different from &b[0]

}

我在 go 1.13 下运行这个程序:


1st element of slice: 0xc000076068

New alloc: &{824634204304 5}

No alloc: &{824634204264 5}

我预计“切片的第一个元素”应该打印出与“No alloc”相同的地址,但实际上它们非常不同。我哪里做错了?


守着一只汪
浏览 275回答 1
1回答

慕沐林林

首先,类型转换调用的是内部函数,在本例中是 slicebytetostring。 https://golang.org/src/runtime/string.go?h=slicebytetostring#L75它将切片的内容复制到新分配的内存中。在第二种情况下,您正在创建切片的新标头并将其转换为字符串标头,即切片内容的新非官方持有者。这样做的问题是垃圾收集器不处理这种情况,并且生成的字符串标题将被标记为与保存实际内容的实际切片没有关系的单个结构,因此,您生成的字符串将仅有效而实际的内容持有者还活着(不要计算这个字符串标题本身)。因此,一旦垃圾收集器扫描了实际内容,您的字符串仍将指向相同的地址,但已释放内存,如果您触摸它,您将收到恐慌错误或未定义的行为。顺便说一句,不需要使用反射包及其头文件,因为直接转换已经创建了新的头文件:*(*string)(unsafe.Pointer(&byte_slice))
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go