Описание пакета 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, не должны изменяться.
Объяснение unsafe
Пакет unsafe
в Go
Пакет unsafe
предоставляет низкоуровневые операции, которые обходят безопасность типов Go. Он используется для взаимодействия с операционной системой, оптимизации производительности или работы с особыми структурами данных.
Основные функции пакета unsafe
:
1. Alignof(x ArbitraryType) uintptr
Что делает: Возвращает требуемое выравнивание для типа переменной x
.
Простыми словами: Компьютер оптимизирует доступ к памяти, размещая данные по определенным адресам (обычно кратным 2, 4, 8 и т.д.). Эта функция показывает, какое выравнивание требуется для типа.
Пример:
type Sample struct {
a bool // 1 байт
b int32 // 4 байта
}
fmt.Println(unsafe.Alignof(Sample{}.b)) // 4 (выравнивание для int32)
2. Offsetof(x ArbitraryType) uintptr
Что делает: Возвращает смещение (в байтах) поля в структуре.
Простыми словами: Показывает, на сколько байтов от начала структуры находится конкретное поле.
Пример:
type Sample struct {
a bool // 1 байт
b int32 // 4 байта
}
fmt.Println(unsafe.Offsetof(Sample{}.b)) // 4 (может быть 4 из-за выравнивания)
3. Sizeof(x ArbitraryType) uintptr
Что делает: Возвращает размер переменной в байтах.
Простыми словами: Показывает, сколько места в памяти занимает переменная.
Пример:
type Sample struct {
a bool // 1 байт
b int32 // 4 байта
}
fmt.Println(unsafe.Sizeof(Sample{})) // 8 (может быть 8 из-за выравнивания)
4. String(ptr *byte, len IntegerType) string
Что делает: Создает строку из указателя на байты и длины.
Простыми словами: Позволяет создать строку напрямую из участка памяти.
Пример:
bytes := []byte{'h', 'e', 'l', 'l', 'o'}
str := unsafe.String(&bytes[0], len(bytes))
fmt.Println(str) // "hello"
5. StringData(str string) *byte
Что делает: Возвращает указатель на базовые байты строки.
Простыми словами: Позволяет получить доступ к внутреннему представлению строки.
Пример:
str := "hello"
ptr := unsafe.StringData(str)
fmt.Println(*ptr) // 104 (байт 'h')
Когда использовать unsafe
?
- Для оптимизации критического по производительности кода
- При работе с системными вызовами или аппаратным обеспечением
- Для сериализации/десериализации данных
- При реализации специальных структур данных
Важно: Использование unsafe
может привести к нестабильности программы и проблемам с безопасностью. Применяйте только когда действительно необходимо и полностью понимаете последствия.
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» не является гарантией того, что код является действительным.
Объяснение Pointer
Тип unsafe.Pointer
в Go - простое объяснение
unsafe.Pointer
- это специальный тип указателя в Go, который позволяет обходить систему типов и работать с памятью напрямую.
Простыми словами
Представьте, что обычные указатели в Go - это строгие охранники: они следят, чтобы вы обращались только к тем данным, тип которых точно соответствует. unsafe.Pointer
- это универсальный пропуск, который позволяет вам обойти эти ограничения и работать с памятью напрямую.
Основные возможности
- Преобразование между разными типами указателей
- Преобразование указателя в
uintptr
и обратно - Работа с памятью на низком уровне
Примеры использования
1. Преобразование типов
package main
import (
"fmt"
"unsafe"
)
func main() {
var x int64 = 42
// Обычный указатель на int64
ptrInt := &x
// Преобразуем в unsafe.Pointer (универсальный указатель)
ptrUnsafe := unsafe.Pointer(ptrInt)
// Теперь можем преобразовать в указатель на другой тип
ptrFloat := (*float64)(ptrUnsafe)
fmt.Printf("Исходное значение: %d\n", x)
fmt.Printf("Как float64: %f\n", *ptrFloat)
}
2. Доступ к структурам
type SecretStruct struct {
a int
b string
c bool
}
func main() {
s := SecretStruct{a: 10, b: "secret", c: true}
// Получаем указатель на структуру
ptr := unsafe.Pointer(&s)
// Получаем доступ к полю 'b' (пропуская проверки типов)
bPtr := (*string)(unsafe.Add(ptr, unsafe.Offsetof(s.b)))
fmt.Println("Секретное поле b:", *bPtr) // Выведет: secret
}
3. Работа с массивами
func main() {
arr := [3]int{1, 2, 3}
// Получаем указатель на первый элемент
ptr := unsafe.Pointer(&arr[0])
// Преобразуем в указатель на байты
bytePtr := (*byte)(ptr)
// Теперь можем работать с памятью как с байтами
fmt.Printf("Первый байт: %x\n", *bytePtr)
}
Важные правила
- Безопасность:
unsafe.Pointer
обходит проверки типов - вы полностью отвечаете за корректность операций. - Совместимость: Гарантии работы есть только при преобразованиях:
- Указатель ↔ unsafe.Pointer ↔ uintptr
- Ограничения: Нельзя арифметику указателей делать напрямую - используйте
unsafe.Add
иunsafe.Slice
.
Когда это полезно?
- Взаимодействие с системными вызовами
- Высокопроизводительные операции
- Специальные структуры данных
- Работа с foreign function interface (FFI)
Предупреждение
unsafe
называется “небезопасным” не просто так. Используйте только когда действительно необходимо и полностью понимаете последствия. Неправильное использование может привести к:
- Падениям программы
- Повреждению данных
- Проблемам с безопасностью
Лучшая практика - изолировать unsafe-код в отдельных пакетах с четкими контрактами.
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 по-прежнему применяются.