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

Convert byte to string using reflect.StringHeader still allocates new memory?

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

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" 相同的地址,但实际上它们非常不同。我哪里错了?

首先,类型转换是调用一个内部函数,在本例中是slicebytetostring。 https://golang.org/src/runtime/string.go?h=slicebytetostring#L75

它确实将 slice 的内容复制到新分配的内存中。

在第二种情况下,您正在创建切片的新 header 并将其转换为字符串 header 切片内容的新非官方持有者。 这样做的问题是垃圾收集器不处理这种情况,结果字符串 header 将被标记为与保存实际内容的实际切片无关的单一结构,因此,您的结果字符串只有当实际内容持有者还活着时才有效(不要计算这个字符串 header 本身)。 因此,一旦垃圾收集器清除了实际内容,您的字符串仍将指向相同的地址但已释放内存,如果您触摸它,您将收到恐慌错误或未定义的行为。

顺便说一句,不需要使用反射包及其 header,因为直接转换已经创建了新的 header 作为结果:

*(*string)(unsafe.Pointer(&byte_slice))