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
Ответ:
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
.