Преобразовать байт в строку, используя отражение. СтрокаHeader все еще выделяет новую память? - PullRequest
0 голосов
/ 19 января 2020

У меня есть небольшой фрагмент кода для проверки 2 способов преобразования байтового среза в строковый объект, одна функция для выделения нового строкового объекта, другая использует небезопасную арифметику указателей c для построения строки *, которая не выделяет новая память:

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}

Я предполагаю, что «1-й элемент слайса» должен распечатать тот же адрес, что и «Нет». Алло c ", но на самом деле они очень разные. Где я ошибся?

1 Ответ

1 голос
/ 20 января 2020

Прежде всего, преобразования типов вызывают внутренние функции, для этого случая это slicebytetostring. https://golang.org/src/runtime/string.go?h=slicebytetostring#L75

Копирует содержимое слайса в новую выделенную память.

Во втором случае вы создаете новый заголовок слайса и приводите его в заголовок строки новый неофициальный держатель содержимого слайса. Проблема в том, что сборщик мусора не обрабатывает подобные случаи, и результирующий заголовок строки будет помечен как единая структура, которая не имеет отношения к реальному фрагменту, который содержит фактическое содержимое, поэтому ваша полученная строка будет действительной только пока фактические владельцы контента живы (не считайте сам заголовок этой строки). Поэтому, как только сборщик мусора сместит фактическое содержимое, ваша строка будет по-прежнему указывать на тот же адрес, но уже освобожденную память, и вы получите ошибку pani c или неопределенное поведение, если дотронетесь до нее.

По Кстати, нет необходимости использовать пакет пакета и его заголовки, потому что прямое приведение уже создает новый заголовок в результате:

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