Описание пакета unsafe языка программирования Go

Пакет unsafe содержит операции, которые обходят типовую безопасность программ Go.

Пакеты, которые импортируют unsafe, могут быть непереносимыми и не защищены правилами совместимости Go 1.

Функции

func Alignof

func Alignof(x ArbitraryType) uintptr

Alignof принимает выражение x любого типа и возвращает требуемое выравнивание гипотетической переменной v, как если бы v была объявлена с помощью var v = x. Это наибольшее значение m, такое что адрес v всегда равен нулю по модулю m. Оно совпадает со значением, возвращаемым reflect.TypeOf(x).Align(). В качестве особого случая, если переменная s имеет тип struct, а f является полем в этой структуре, то Alignof(s.f) вернет требуемое выравнивание поля этого типа в структуре. Этот случай аналогичен значению, возвращаемому reflect.TypeOf(s.f).FieldAlign(). Возвращаемое значение Alignof является константой Go, если тип аргумента не имеет переменного размера. (Определение типов переменного размера см. в описании Sizeof.)

func Offsetof

func Offsetof(x ArbitraryType) uintptr

Offsetof возвращает смещение в структуре поля, представленного x, которое должно иметь форму structValue.field. Другими словами, она возвращает количество байтов между началом структуры и началом поля. Возвращаемое значение Offsetof является константой Go, если тип аргумента x не имеет переменного размера. (Определение типов переменного размера см. в описании Sizeof.)

func Sizeof

func Sizeof(x ArbitraryType) uintptr

Sizeof принимает выражение x любого типа и возвращает размер в байтах гипотетической переменной v, как если бы v была объявлена с помощью var v = x. Размер не включает в себя память, на которую может ссылаться x. Например, если x является срезом, Sizeof возвращает размер дескриптора среза, а не размер памяти, на которую ссылается срез; если x является интерфейсом, Sizeof возвращает размер самого значения интерфейса, а не размер значения, хранящегося в интерфейсе. Для структуры размер включает в себя любую заполняющую пробел, введенную выравниванием полей. Возвращаемое значение Sizeof является константой Go, если тип аргумента x не имеет переменного размера. (Тип имеет переменный размер, если он является параметром типа или если он является типом массива или структуры с элементами переменного размера).

func String

func String(ptr *byte, len IntegerType) string

String возвращает строковое значение, байты которого начинаются с ptr и длина которого равна len.

Аргумент len должен быть целочисленного типа или нетипизированной константой. Константа len должна быть неотрицательной и представляться значением типа int; если это константа без типа, ей присваивается тип int. Во время выполнения, если len отрицательна или если ptr равна nil, а len не равна нулю, возникает паника во время выполнения.

Поскольку строки Go являются неизменяемыми, байты, переданные в String, не должны изменяться, пока существует возвращаемое строковое значение. 6

func StringData

func StringData(str string) *byte

StringData возвращает указатель на базовые байты str. Для пустой строки возвращаемое значение не определено и может быть nil.

Поскольку строки Go являются неизменяемыми, байты, возвращаемые StringData, не должны изменяться.

type ArbitraryType

type ArbitraryType int

ArbitraryType используется здесь только в целях документирования и на самом деле не входит в состав пакета unsafe. Он представляет тип произвольного выражения Go.

func Slice

func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

Функция Slice возвращает срез, базовый массив которого начинается с ptr, а длина и емкость — len. Slice(ptr, len) эквивалентно

(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:] за исключением того, что в особом случае, если ptr равно nil, а len равно нулю, Slice возвращает nil.

Аргумент len должен быть целочисленного типа или нетипизированной константой. Константа len должна быть неотрицательной и представляться значением типа int; если это нетипизированная константа, ей присваивается тип int. Во время выполнения, если len отрицательно, или если ptr равно nil, а len не равно нулю, возникает паника во время выполнения.

func SliceData

func SliceData(slice []ArbitraryType) *ArbitraryType

SliceData возвращает указатель на базовый массив аргумента slice.

Если cap(slice) > 0, SliceData возвращает &slice[:1][0]. Если slice == nil, SliceData возвращает nil. В противном случае SliceData возвращает не нулевой указатель на неуказанный адрес памяти.

type IntegerType

type IntegerType int

IntegerType используется здесь только для целей документирования и на самом деле не является частью пакета unsafe. Он представляет любой произвольный целочисленный тип.

type Pointer

type Pointer *ArbitraryType

Pointer представляет указатель на произвольный тип. Для типа Pointer доступны четыре специальные операции, которые недоступны для других типов:

  • Значение указателя любого типа может быть преобразовано в Pointer.
  • Pointer может быть преобразован в значение указателя любого типа.
  • uintptr может быть преобразован в Pointer.
  • Pointer может быть преобразован в uintptr.

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

Следующие шаблоны, связанные с Pointer, являются действительными. Код, не использующий эти шаблоны, вероятно, является недействительным сегодня или станет недействительным в будущем. Даже действительные шаблоны, приведенные ниже, сопровождаются важными предостережениями.

Запуск «go vet» может помочь найти использования Pointer, которые не соответствуют этим шаблонам, но отсутствие сообщений от «go vet» не является гарантией того, что код является действительным.

1. Преобразование *T1 в Pointer в *T2.

При условии, что T2 не больше T1 и что оба имеют одинаковую структуру памяти, это преобразование позволяет переосмыслить данные одного типа как данные другого типа. Примером является реализация math.Float64bits:

func Float64bits(f float64) uint64
    return *(*uint64)(unsafe.Pointer(&f))
}

2. Преобразование указателя в uintptr (но не обратно в указатель).

Преобразование указателя в uintptr дает адрес памяти указанного значения в виде целого числа. Обычно uintptr используется для вывода на печать.

Преобразование uintptr обратно в указатель в целом не допускается.

uintptr — это целое число, а не ссылка. Преобразование указателя в uintptr создает целое значение без семантики указателя. Даже если uintptr содержит адрес какого-либо объекта, сборщик мусора не обновит значение uintptr, если объект переместится, и uintptr не помешает объекту быть восстановленным.

Остальные шаблоны перечисляют единственные допустимые преобразования из uintptr в Pointer.

3. Преобразование Pointer в uintptr и обратно с помощью арифметики.

Если p указывает на выделенный объект, его можно продвинуть по объекту путем преобразования в uintptr, добавления смещения и преобразования обратно в Pointer.

p = unsafe.Pointer(uintptr(p) + offset)

Наиболее распространенное использование этого шаблона — доступ к полям в структуре или элементам массива:

// эквивалентно f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))

// эквивалентно e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))

Таким образом можно как добавлять, так и вычитать смещения из указателя. Также допустимо использовать &^ для округления указателей, обычно для выравнивания. Во всех случаях результат должен продолжать указывать на исходный выделенный объект.

В отличие от C, не допускается перемещение указателя за пределы его исходного выделения:

// НЕПРАВИЛЬНО: конечная точка находится за пределами выделенного пространства.
var s thing
end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))

// НЕПРАВИЛЬНО: конец указывает за пределы выделенного пространства.
b := make([]byte, n)
end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

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

// НЕПРАВИЛЬНО: uintptr не может быть сохранен в переменной
// до преобразования обратно в Pointer.
u := uintptr(p)
p = unsafe.Pointer(u + offset)

Обратите внимание, что указатель должен указывать на выделенный объект, поэтому он не может быть nil.

// НЕПРАВИЛЬНО: преобразование указателя nil
u := unsafe.Pointer(nil)
p := unsafe.Pointer(uintptr(u) + offset)

4. Преобразование указателя в uintptr при вызове функций типа syscall.Syscall.

Функции Syscall в пакете syscall передают свои аргументы uintptr непосредственно операционной системе, которая затем, в зависимости от деталей вызова, может переинтерпретировать некоторые из них как указатели. То есть реализация системного вызова неявно преобразует определенные аргументы обратно из uintptr в указатель.

Если аргумент-указатель должен быть преобразован в uintptr для использования в качестве аргумента, это преобразование должно появиться в самом выражении вызова:

syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

Компилятор обрабатывает указатель, преобразованный в uintptr в списке аргументов вызова функции, реализованной на ассемблере, путем обеспечения того, что ссылка на выделенный объект, если таковой имеется, сохраняется и не перемещается до завершения вызова, даже если по типам кажется, что объект больше не нужен во время вызова.

Чтобы компилятор распознал этот паттерн, преобразование должно появиться в списке аргументов:

// НЕПРАВИЛЬНО: uintptr не может быть сохранен в переменной
// до неявного преобразования обратно в Pointer во время системного вызова.
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

5. Преобразование результата reflect.Value.Pointer или reflect.Value.UnsafeAddr из uintptr в Pointer.

Методы Value пакета reflect с именами Pointer и UnsafeAddr возвращают тип uintptr вместо unsafe.Pointer, чтобы вызывающие функции не могли изменить результат на произвольный тип без предварительного импорта «unsafe». Однако это означает, что результат является неустойчивым и должен быть преобразован в Pointer сразу после вызова, в том же выражении:

p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

Как и в приведенных выше случаях, хранение результата до преобразования является недопустимым:

// НЕПРАВИЛЬНО: uintptr не может быть сохранен в переменной
// до преобразования обратно в Pointer.
u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))

6. Преобразование поля данных reflect.SliceHeader или reflect.StringHeader в Pointer или из Pointer.

Как и в предыдущем случае, структуры данных reflect SliceHeader и StringHeader объявляют поле Data как uintptr, чтобы вызывающие функции не могли изменить результат на произвольный тип без предварительного импорта «unsafe». Однако это означает, что SliceHeader и StringHeader действительны только при интерпретации содержимого фактического значения slice или string.

var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // случай 1
hdr.Data = uintptr(unsafe.Pointer(p))              // случай 6 (этот случай)
hdr.Len = n

В этом случае hdr.Data на самом деле является альтернативным способом ссылки на базовый указатель в заголовке строки, а не самой переменной uintptr.

В общем случае reflect.SliceHeader и reflect.StringHeader следует использовать только как *reflect.SliceHeader и *reflect.StringHeader, указывающие на фактические срезы или строки, но никогда как простые структуры. Программа не должна объявлять или выделять переменные этих типов структур.

// НЕПРАВИЛЬНО: непосредственно объявленный заголовок не будет содержать Data в качестве ссылки.
var hdr reflect.StringHeader
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n
s := *(*string)(unsafe.Pointer(&hdr)) // p, возможно, уже утрачен

func Add

func Add(ptr Pointer, len IntegerType) Pointer

Функция Add добавляет len к ptr и возвращает обновленный указатель Pointer(uintptr(ptr) + uintptr(len)). Аргумент len должен быть целочисленного типа или нетипизированной константой. Константный аргумент len должен быть представлен значением типа int; если он является нетипизированной константой, ему присваивается тип int. Правила допустимого использования Pointer по-прежнему применяются.