コンテンツにスキップ

Go/uintptr

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

Goのuintptrについて

[編集]

Goにおけるuintptrは、ポインタを表現するのに十分なサイズを持つ符号なし整数型の組み込み型です。主にポインタ演算やシステムプログラミングで使用されます。

基本情報

[編集]
  • uintptrはポインタを保持できる大きさの符号なし整数型
  • サイズはプラットフォームに依存(32ビットシステムでは32ビット、64ビットシステムでは64ビット)
  • メモリアドレスを整数として表現・操作するために使用
  • ガベージコレクタによる追跡がされない(通常のポインタと異なる)

他のキーワードとの組み合わせ

[編集]

変数宣言との組み合わせ

[編集]
var address uintptr = 0x1000
ptr := uintptr(0xFFFFFFFF)

定数定義との組み合わせ

[編集]
const NULL_PTR uintptr = 0
const MEMORY_OFFSET uintptr = 8

関数パラメータと戻り値

[編集]
func addOffset(base uintptr, offset uintptr) uintptr {
    return base + offset
}

func getBaseAddress() uintptr {
    // メモリアドレスを返す処理
    return 0x1000
}

型変換

[編集]
// ポインタからuintptrへの変換
x := 42
ptr := &x
addrInt := uintptr(unsafe.Pointer(ptr))

// uintptrからポインタへの変換(unsafe.Pointerを経由)
var addr uintptr = 0x1234
p := unsafe.Pointer(addr)
intPtr := (*int)(p)

unsafe.Pointerとの連携

[編集]
import "unsafe"

func main() {
    s := "Hello, world!"
    // 文字列へのポインタをuintptrに変換
    stringPtr := unsafe.Pointer(&s)
    addr := uintptr(stringPtr)
    
    // uintptrを使ってポインタ演算
    nextAddr := addr + 8
    // uintptrからunsafe.Pointerに戻す
    nextPtr := unsafe.Pointer(nextAddr)
}

構造体のフィールド

[編集]
type MemoryBlock struct {
    Start uintptr
    Size  uintptr
}

type Handle struct {
    Address uintptr
    ID      int
}

一般的なユースケース

[編集]
  1. メモリアドレス操作とポインタ演算
   import "unsafe"
   
   func main() {
       arr := [4]int{1, 2, 3, 4}
       // 配列の先頭アドレスを取得
       baseAddr := uintptr(unsafe.Pointer(&arr[0]))
       
       // 2番目の要素へのポインタを計算(intのサイズ分オフセット)
       secondElemAddr := baseAddr + unsafe.Sizeof(arr[0])
       secondElemPtr := (*int)(unsafe.Pointer(secondElemAddr))
       
       *secondElemPtr = 22 // arr[1]を22に変更
       fmt.Println(arr) // [1 22 3 4]
   }
  1. スライスの内部表現へのアクセス
   import "unsafe"
   
   // スライスの内部表現(非公式)
   type SliceHeader struct {
       Data uintptr
       Len  int
       Cap  int
   }
   
   func main() {
       s := []int{1, 2, 3, 4, 5}
       // スライスヘッダへのポインタ取得
       hdr := (*SliceHeader)(unsafe.Pointer(&s))
       
       fmt.Printf("スライスのデータポインタ: %x\n", hdr.Data)
       fmt.Printf("スライスの長さ: %d\n", hdr.Len)
       fmt.Printf("スライスの容量: %d\n", hdr.Cap)
   }
  1. システムコールインターフェース
   import (
       "syscall"
       "unsafe"
   )
   
   func main() {
       var info syscall.Stat_t
       path := "/tmp"
       pathPtr := unsafe.Pointer(syscall.StringBytePtr(path))
       _, _, errno := syscall.Syscall(syscall.SYS_STAT, uintptr(pathPtr), uintptr(unsafe.Pointer(&info)), 0)
       if errno != 0 {
           fmt.Println("Error:", errno)
       }
   }
  1. C言語との相互運用(CGO)
   // #include <stdlib.h>
   // void* my_c_function(void* ptr, size_t offset);
   //
   import "C"
   import "unsafe"
   
   func main() {
       data := []byte{1, 2, 3, 4}
       dataPtr := unsafe.Pointer(&data[0])
       
       // C関数に渡すためにuintptrに変換
       result := C.my_c_function(dataPtr, C.size_t(8))
       resultAddr := uintptr(result)
       
       fmt.Printf("結果アドレス: %x\n", resultAddr)
   }
  1. メモリマッピング
   import (
       "fmt"
       "syscall"
       "unsafe"
   )
   
   func main() {
       const size = 4096
       
       // メモリをマップ
       addr, _, errno := syscall.Syscall6(
           syscall.SYS_MMAP,
           0,                           // アドレスヒント(0=任意)
           uintptr(size),               // マップするサイズ
           uintptr(syscall.PROT_READ|syscall.PROT_WRITE), // 保護フラグ
           uintptr(syscall.MAP_PRIVATE|syscall.MAP_ANONYMOUS), // マップフラグ
           ^uintptr(0),                 // ファイルディスクリプタ(-1)
           0,                           // オフセット
       )
       
       if errno != 0 {
           fmt.Println("mmap failed:", errno)
           return
       }
       
       // マップされたメモリにアクセス
       buffer := (*[size]byte)(unsafe.Pointer(addr))
       buffer[0] = 42
       
       // メモリを解放
       syscall.Syscall(
           syscall.SYS_MUNMAP,
           addr,
           uintptr(size),
           0,
       )
   }
  1. 構造体のアライメント計算
   import (
       "fmt"
       "unsafe"
   )
   
   type MyStruct struct {
       A byte
       B int64
       C int32
   }
   
   func main() {
       var s MyStruct
       
       // 構造体の先頭アドレス
       baseAddr := uintptr(unsafe.Pointer(&s))
       
       // フィールドのオフセットを計算
       aOffset := uintptr(unsafe.Offsetof(s.A))
       bOffset := uintptr(unsafe.Offsetof(s.B))
       cOffset := uintptr(unsafe.Offsetof(s.C))
       
       fmt.Printf("ベースアドレス: %x\n", baseAddr)
       fmt.Printf("A のオフセット: %d\n", aOffset)
       fmt.Printf("B のオフセット: %d\n", bOffset) // おそらく8(アライメントのため)
       fmt.Printf("C のオフセット: %d\n", cOffset)
   }
  1. ハードウェアアクセス(組み込みシステム)
   const (
       GPIO_BASE_ADDR uintptr = 0x3F200000 // 例: Raspberry PiのGPIOベースアドレス
       GPIO_SET_OFFSET uintptr = 0x1C
       GPIO_CLR_OFFSET uintptr = 0x28
   )
   
   func setGPIO(pin uint) {
       // GPIOレジスタのセットアドレスを計算
       setReg := unsafe.Pointer(GPIO_BASE_ADDR + GPIO_SET_OFFSET)
       // ピンに対応するビットをセット
       regVal := (*uint32)(setReg)
       *regVal = 1 << pin
   }
   
   func clearGPIO(pin uint) {
       // GPIOレジスタのクリアアドレスを計算
       clrReg := unsafe.Pointer(GPIO_BASE_ADDR + GPIO_CLR_OFFSET)
       // ピンに対応するビットをクリア
       regVal := (*uint32)(clrReg)
       *regVal = 1 << pin
   }
  1. 反射を使った構造体マニピュレーション
   import (
       "fmt"
       "reflect"
       "unsafe"
   )
   
   type Person struct {
       Name string
       Age  int
   }
   
   func main() {
       p := Person{Name: "Alice", Age: 30}
       
       // 反射を使ってAgeフィールドのアドレスを取得
       v := reflect.ValueOf(&p).Elem()
       ageField := v.FieldByName("Age")
       
       // フィールドのポインタをuintptrに変換
       ageAddr := unsafe.Pointer(ageField.UnsafeAddr())
       agePtr := (*int)(ageAddr)
       
       // 値を変更
       *agePtr = 31
       
       fmt.Println(p) // {Alice 31}
   }

安全でないunsafe.Pointerとuintptrの変換パターン

[編集]

Goの仕様で安全とされているunsafe.Pointerとuintptrの変換パターン:

  1. ポインタからuintptrへの変換
       ptr := &someVar
       addr := uintptr(unsafe.Pointer(ptr))
    
  2. uintptrからポインタへの変換
       var addr uintptr = ...
       ptr := unsafe.Pointer(addr)
    
  3. ポインタ演算
       p := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.Field))
    

注意点

[編集]
  1. ガベージコレクションの問題:
    uintptrはただの整数型であり、ガベージコレクタによって追跡されません。そのため、元の参照がなくなるとメモリが回収される可能性があります。
       // 危険なコード
       u := uintptr(unsafe.Pointer(&someVar))
       // ここでGCが実行されると、someVarが回収される可能性がある
       p := unsafe.Pointer(u) // ダングリングポインタになる可能性
    
  2. ポインタ変換の順序:
    変換は直接行う必要があります。変数に一時的に格納すると危険です。
       // 安全なコード
       p := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + offset)
       
       // 危険なコード
       temp := uintptr(unsafe.Pointer(&s)) + offset
       // ここでGCが走る可能性
       p := unsafe.Pointer(temp) // 危険
    
  3. メモリアクセス違反:
    無効なアドレスにアクセスするとプログラムがクラッシュします。
       var badAddr uintptr = 1
       p := unsafe.Pointer(badAddr)
       val := *(*int)(p) // プログラムクラッシュ
    
  4. ポータビリティの問題:
    ポインタサイズはプラットフォームに依存します(32ビットか64ビット)。
  5. コンパイラの最適化:
    コンパイラはunsafeパッケージの使用を前提とした最適化を行わないため、パフォーマンスに影響する可能性があります。

uintptrはパワフルですが危険なため、通常は必要な場合(主にシステムプログラミングや最適化が必要な場合)にのみ使用し、多くの場合は通常のポインタ型(*T)を使用することが推奨されます。