Почему reflect и unsafe выводит разные значения у одного объекта в Golang?
@Golang
Рассмотрим код:
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.