Тип uintptr

Тип uintptr представляет собой целое беззнаковое число, которое может хранить битовое представление указателя. Этот тип используется в основном для низкоуровневых операций с памятью и взаимодействия с системными вызовами. Важно понимать, что uintptr не является указателем, а лишь числовым значением адреса.

Особенности типа uintptr

Основное отличие uintptr от обычных указателей заключается в том, что значения этого типа не учитываются сборщиком мусора. Это означает, что если вы храните адрес объекта в uintptr, сборщик мусора не будет считать этот объект достижимым и может его удалить. Поэтому использовать uintptr для хранения указателей без дополнительных мер предосторожности опасно.

Синтаксис

govar ptr uintptr
ptr = uintptr(unsafe.Pointer(&variable))

Пример преобразования указателя в uintptr

Давайте преобразуем указатель на целое число в тип uintptr и выведем его значение:

gopackage main

import (
  "fmt"
  "unsafe"
)

func main() {
  num := 42
  ptr := uintptr(unsafe.Pointer(&num))
  fmt.Println("Address:", ptr)
}

Результат выполнения кода:

go"Address: 824633835080"

В результате мы получили числовое значение адреса переменной num в памяти.

Пример работы с памятью через uintptr

Продемонстрируем, как с помощью uintptr можно выполнять арифметические операции с адресами:

gopackage 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)
}

Результат выполнения кода:

go"First element address: 824633835120"
"Second element address: 824633835128"

В примере мы получили адрес первого элемента среза и затем вычислили адрес второго элемента, прибавив размер одного элемента типа int.

Пример опасности использования uintptr

Покажем, почему использовать uintptr для хранения адресов без осторожности опасно:

gopackage main

import (
  "fmt"
  "unsafe"
)

func main() {
  var ptr uintptr
  
  func() {
    num := 100
    ptr = uintptr(unsafe.Pointer(&num))
  }()
  
  // После выхода из функции переменная num может быть удалена
  // сборщиком мусора, а ptr все еще хранит адрес
  fmt.Println("Stored address:", ptr)
}

Результат выполнения кода:

go"Stored address: 824633835080"

После выхода из анонимной функции переменная num больше не используется, и сборщик мусора может освободить память по этому адресу. Однако ptr все еще хранит числовое значение адреса, что может привести к ошибкам при попытке обращения к этой памяти.

Пример безопасного использования uintptr

Безопасный способ использования uintptr - преобразование сразу перед операцией, без сохранения значения на длительное время:

gopackage 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)
}

Результат выполнения кода:

go"Restored value: 42"

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

Смотрите также

  • тип pointer,
    который представляет собой непосредственный указатель на данные
  • тип unsafe.Pointer,
    который позволяет выполнять низкоуровневые операции с указателями
  • тип int64,
    который является 64-битным целым числом со знаком
  • тип uint64,
    который является 64-битным целым числом без знака