Тип uintptr
Тип uintptr представляет собой целое беззнаковое число,
которое может хранить битовое представление указателя.
Этот тип используется в основном для низкоуровневых операций
с памятью и взаимодействия с системными вызовами.
Важно понимать, что uintptr не является указателем,
а лишь числовым значением адреса.
Особенности типа uintptr
Основное отличие uintptr от обычных указателей заключается
в том, что значения этого типа не учитываются сборщиком мусора.
Это означает, что если вы храните адрес объекта в uintptr,
сборщик мусора не будет считать этот объект достижимым и может
его удалить. Поэтому использовать uintptr для хранения
указателей без дополнительных мер предосторожности опасно.
Синтаксис
var ptr uintptr
ptr = uintptr(unsafe.Pointer(&variable))
Пример преобразования указателя в uintptr
Давайте преобразуем указатель на целое число в тип uintptr
и выведем его значение:
package main
import (
"fmt"
"unsafe"
)
func main() {
num := 42
ptr := uintptr(unsafe.Pointer(&num))
fmt.Println("Address:", ptr)
}
Результат выполнения кода:
"Address: 824633835080"
В результате мы получили числовое значение адреса переменной
num в памяти.
Пример работы с памятью через uintptr
Продемонстрируем, как с помощью uintptr можно выполнять
арифметические операции с адресами:
package main
import (
"fmt"
"unsafe"
)
func main() {
arr := []int{10, 20, 30}
ptr := uintptr(unsafe.Pointer(&arr[0]))
fmt.Println("First element address:", ptr)
// Смещение на размер одного элемента
ptr2 := ptr + unsafe.Sizeof(arr[0])
fmt.Println("Second element address:", ptr2)
}
Результат выполнения кода:
"First element address: 824633835120"
"Second element address: 824633835128"
В примере мы получили адрес первого элемента среза и затем
вычислили адрес второго элемента, прибавив размер одного
элемента типа int.
Пример опасности использования uintptr
Покажем, почему использовать uintptr для хранения адресов
без осторожности опасно:
package main
import (
"fmt"
"unsafe"
)
func main() {
var ptr uintptr
func() {
num := 100
ptr = uintptr(unsafe.Pointer(&num))
}()
// После выхода из функции переменная num может быть удалена
// сборщиком мусора, а ptr все еще хранит адрес
fmt.Println("Stored address:", ptr)
}
Результат выполнения кода:
"Stored address: 824633835080"
После выхода из анонимной функции переменная num больше
не используется, и сборщик мусора может освободить память по этому
адресу. Однако ptr все еще хранит числовое значение адреса,
что может привести к ошибкам при попытке обращения к этой памяти.
Пример безопасного использования uintptr
Безопасный способ использования uintptr - преобразование
сразу перед операцией, без сохранения значения на длительное время:
package main
import (
"fmt"
"unsafe"
)
func main() {
num := 42
// Преобразуем и сразу используем значение
ptr := unsafe.Pointer(&num)
uintptrVal := uintptr(ptr)
// Сразу восстанавливаем указатель
restoredPtr := unsafe.Pointer(uintptrVal)
restoredNum := *(*int)(restoredPtr)
fmt.Println("Restored value:", restoredNum)
}
Результат выполнения кода:
"Restored value: 42"
В этом примере мы выполняем преобразования в одном контексте,
не сохраняя uintptr на длительное время, что гарантирует
безопасность работы с памятью.