Как создать массив или фрагмент из массива unsafe.Pointer в golang? - PullRequest
0 голосов
/ 05 июля 2018

Указатель на массив, скажем:

p := uintptr(unsafe.Pointer(&array))
size := 5

Я не могу получить доступ к переменной array, приведенный выше код используется, чтобы сделать ее более понятной.

Кроме того, я знаю размер массива, но size не является постоянной величиной, она изменяется в зависимости от времени выполнения.

Теперь я хочу инициализировать фрагмент или массив с известным указателем, размером и, конечно, типом данных.

Я придумываю следующий код:

data := make([]byte, size)
stepSize := unsafe.Sizeof(data[0])
for i := 0; i < size; i++ {
    data[i] = *(*byte)(unsafe.Pointer(p))
    p += stepSize
}
fmt.println(data)

но этот метод делает копирование памяти, которое может быть неэффективным, есть ли в любом случае без копирования?

P.S. Я также попробовал следующие два метода,

// method 1
data := *(*[]byte)(unsafe.Pointer(p))
// method 2
data := *(*[size]byte)(unsafe.Pointer(p))

но во время выполнения произойдет сбой, и теперь я знаю его причину.

1 Ответ

0 голосов
/ 05 июля 2018

Предисловие:

Вы должны знать: если вы получаете указатель как значение типа 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
...