Предисловие:
Вы должны знать: если вы получаете указатель как значение типа uintptr
, это не мешает исходному массиву собирать мусор (значение uintptr
не считается ссылкой). Поэтому будьте осторожны при использовании такого значения, нет гарантии, что оно будет указывать на действительное значение / область памяти.
Цитирование из пакета unsafe.Pointer
:
uintptr - это целое число, а не ссылка. Преобразование указателя в uintptr создает целочисленное значение без семантики указателя. Даже если uintptr содержит адрес какого-либо объекта, сборщик мусора не будет обновлять значение этого uintptr, если объект перемещается, и этот uintptr не будет препятствовать восстановлению объекта.
Общий совет: держитесь подальше от пакета unsafe
как можно больше. Оставайтесь внутри безопасности типа Го.
Объявите переменную типа среза и используйте небезопасные преобразования для получения ее дескриптора reflect.SliceHeader
.
Затем вы можете изменить его поля, используя указатель в качестве значения SliceHeader.Data
, а размер как SliceHeader.Len
и SliceHeader.Cap
.
Как только вы закончите с этим, переменная slice будет указывать на тот же массив, что и ваш начальный указатель.
arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
size := len(arr)
p := uintptr(unsafe.Pointer(&arr))
var data []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
sh.Data = p
sh.Len = size
sh.Cap = size
fmt.Println(data)
runtime.KeepAlive(arr)
Вывод (попробуйте на Go Playground ):
[0 1 2 3 4 5 6 7 8 9]
Обратите внимание, что я использовал runtime.KeepAlive()
. Это происходит потому, что после взятия адреса arr
и получения его длины мы больше не ссылаемся на arr
(p
, будучи uintptr
, не считается ссылкой), и агрессивный сборщик мусора может - по праву - сотрите arr
, прежде чем мы доберемся до точки для печати data
(указывая на arr
). Помещение runtime.KeepAlive()
в конец main()
гарантирует, что arr
не будет собирать мусор
до этого звонка. Подробнее см. В Go, когда переменная станет недоступной? Вам не нужно вызывать runtime.KeepAlive()
в своем коде, если поставщик указателя гарантирует, что она не будет собирать мусор.
В качестве альтернативы вы можете создать reflect.SliceHeader
с составным литералом и использовать небезопасные преобразования для получения среза из него, например:
sh := &reflect.SliceHeader{
Data: p,
Len: size,
Cap: size,
}
data := *(*[]byte)(unsafe.Pointer(sh))
fmt.Println(data)
runtime.KeepAlive(arr)
Вывод будет таким же. Попробуйте это на Go Playground .
Этот вариант / случай использования задокументирован в unsafe.Pointer
, с предостережениями и предупреждениями:
(6) Преобразование поля данных refle.SliceHeader или refle.StringHeader в указатель или из него.
Как и в предыдущем случае, отражающие структуры данных SliceHeader и StringHeader объявляют поле Data как uintptr, чтобы вызывающие абоненты не могли изменить результат на произвольный тип без предварительного импорта «unsafe». Однако это означает, что SliceHeader и StringHeader действительны только при интерпретации содержимого фактического среза или строкового значения.
var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1
hdr.Data = uintptr(unsafe.Pointer(p)) // case 6 (this case)
hdr.Len = n
В этом случае hdr.Data - это альтернативный способ ссылки на базовый указатель в заголовке слайса, а не на саму переменную uintptr.
В общем, рефлексы.SliceHeader и refle.StringHeader должны использоваться только как * refle.SliceHeader и * refle.StringHeader, указывающие на реальные фрагменты или строки, а не на простые структуры. Программа не должна объявлять или размещать переменные этих структурных типов.
// INVALID: a directly-declared header will not hold Data as a reference.
var hdr reflect.StringHeader
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n
s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost