Golang reflect и unsafe.

Рассмотрим код:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    s := "hello, world!"
    hello := s[:5]
    world := s[7:]
    fmt.Printf("s = `%s`, sh = %v, sp = %d\n",
        s,
        *(*reflect.StringHeader)(unsafe.Pointer(&s)),
        reflect.ValueOf(&s).Pointer(),
    )

    fmt.Printf("hello = `%s`, helloh = %v, hellop = %d\n",
        hello,
        *(*reflect.StringHeader)(unsafe.Pointer(&hello)),
        reflect.ValueOf(&hello).Pointer(),
    )

    fmt.Printf("world = `%s`, worldh = %v, worldp = %d\n",
        world,
        *(*reflect.StringHeader)(unsafe.Pointer(&world)),
        reflect.ValueOf(&world).Pointer(),
    )

    slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    s1 := slice[:5]
    s2 := slice[4:]
    fmt.Printf("slice = `%v`, sh = %v, sp = %d\n",
        slice,
        *(*reflect.SliceHeader)(unsafe.Pointer(&slice)),
        reflect.ValueOf(slice).Pointer(),
    )

    fmt.Printf("s1 = `%v`, s1h = %v, s1p = %d\n",
        s1,
        *(*reflect.SliceHeader)(unsafe.Pointer(&s1)),
        reflect.ValueOf(s1).Pointer(),
    )

    fmt.Printf("s2 = `%v`, s2h = %v, s2p = %d\n",
        s2,
        *(*reflect.SliceHeader)(unsafe.Pointer(&s2)),
        reflect.ValueOf(s2).Pointer(),
    )
}

вывод

s = `hello, world!`, sh = {4817792 13}, sp = 824633786960
hello = `hello`, helloh = {4817792 5}, hellop = 824633786976
world = `world!`, worldh = {4817799 6}, worldp = 824633786992

slice = `[1 2 3 4 5 6 7 8 9 10]`, sh = {824633860176 10 10}, sp = 824633860176
s1 = `[1 2 3 4 5]`, s1h = {824633860176 5 10}, s1p = 824633860176
s2 = `[5 6 7 8 9 10]`, s2h = {824633860208 6 6}, s2p = 824633860208

Ответ:

@Golang

reflect.StringHeader

type StringHeader struct {
    Data uintptr
    Len  int
}

Строки s и s[:5] начинаются в памяти в одном и том же месте, поэтому у них одинаковое значение указателя Data. Переменная world равна s[7:], то есть у этой строки указатель данных сдвинут на 7 байтов относительно строки s. Именно это показывает вывод вашей программы: данные строки s начинается с адреса 4817792, а строки s[7:] с 4817799.

Тип SliceHeader устроен аналогично:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Указатель на данные, текущая длина, максимальная возможная длина (можно делать append без реаллокации до тех пор, пока Len <= Cap)

Данные для slice[:5] и slice начинаются с одного и того же адреса. Поэтому у них одинаковые значения Data и Cap. Кстати, это означает, что если вы сделаете append(s1, 123), то значение slice тоже изменится, ведь у них общий буфер.

Данные для slice[4:] начинаются на четыре int-а дальше, что на 64-х битной машине равно 4*8=32 байта: 824633860176 + 32 = 824633860208

Буфер у slice и slice[4:] общий, поэтому максимальная длина slice[4:] на четыре int-а меньше: slice[4:].Cap == slice.Cap - 4.

Как-то так.

В коде Value.Pointer используется по-разному. В случае reflect.ValueOf(&s).Pointer() указатель на s, а не на данные s. В случае reflect.ValueOf(slice).Pointer() вы получаете указатель именно на данные среза.

Про указатель на объект и указатель на данные.

В Go объект типа string и массив байтов, которые этой строкой являются, лежат в разных местах. Аналогично с массивами – переменная типа массив ссылается на SliceHeader, а конкретные байты лежат совсем в другом месте.

Указатель на объект string – это unsafe.Pointer(&s). Физически по этому указателю лежит структура StringHeader. Данные лежат по адресу StringHeader.Data. Ваш пример это отлично показывает – объект лежит по адресу sp = 824633786960, а данные по адресу sh.Data = 4817792.

Теперь о том, что получилось в случае с массивами. В интерфейсе reflect.Value есть метод Pointer. Этот метод очень особенный: он работает для функций, указателей и массивов/срезов. Для остальных типов он паникует. Попробуйте reflect.ValueOf(s).Pointer(), убедитесь сами.

Для массивов этот метод возвращает указатель на данные, а не указатель на объек. Другими словами, этот метод возвращает SliceHeader.Data способом, который не зависит от реализации массивов в рантайме Go. Возможно, в будущем они что-нибудь поменяют, или вовсе отменят SliceHeader, а Pointer() будет по-прежнему работать. Итак, для массивов “канонный” способ получить указатель на данные – reflect.ValueOf(slice).Pointer()

Обратите внимание, что для массивов применяется метод Pointer непосредственно к объекту типа []int. Поэтому он давал указатель на данные. В случае строк применяется метод Pointer к объекту типа *string. Для указателей метод Pointer возвращает сам указатель.

Для строк нет стандартного способа получить указатель на данные, есть только костыль ((*reflect.StringHeader)(unsafe.Pointer(&obj)).Data.

Источник

+1
0
+1
2
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *