unsafe 翻訳

pkg.go.dev

Ver1.17

概要

unsafeパッケージには、Goプログラムの型安全性を回避する操作が含まれています。

unsafeをインポートしたパッケージは移植不可能な場合があり、Go 1の互換性ガイドラインでは保護されません。

関数

type Pointer

type Pointer *ArbitraryType

Pointerは、任意の型へのポインタを表します。Pointer型では、他の型にはない4つの特別な操作が可能です。

- 任意の型のポインタ値をPointerに変換することができます。
- Pointerは、任意の型のポインタ値に変換できます。
- uintptrはPointerに変換できます。
- Pointerはuintptrに変換できます。

そのため、ポインターを使うと、プログラムが型システムを破り、任意のメモリを読み書きできるようになります。使用には細心の注意が必要です。

Pointer を含む以下のパターンは有効です。これらのパターンを使用していないコードは、現在無効であるか、将来的に無効になる可能性があります。以下の有効なパターンにも重要な注意点があります。

"go vet"を実行すると、これらのパターンに適合しないPointerの使い方を見つけることができますが、"go vet "が沈黙していても、コードが有効であることを保証するものではありません。

(1) T1をT2へのPointerに変換する。

T2がT1よりも大きくなく、2つのメモリレイアウトが同等であることを条件に、この変換により、ある型のデータを別の型のデータとして再解釈することができます。例として,math.Float64bitsの実装があります。

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

(2)Pointerからuintptrへの変換(Pointerには戻らない)。

Pointerをuintptrに変換すると、指し示された値のメモリアドレスが整数として生成されます。このようなuintptrの通常の用途は、それを表示(プリント)することです。

uintptrをPointerに戻す変換は、一般的には有効ではありません。

uintptrは整数であり、参照ではありません。Pointerをuintptrに変換すると、ポインタのセマンティクスを持たない整数値が作成されます。uintptrがあるオブジェクトのアドレスを保持していても、オブジェクトが移動してもガベージコレクタはuintptrの値を更新しませんし、uintptrはオブジェクトの再取得を防ぎません。

残りのパターンは,uintptrからPointerへの唯一の有効な変換を列挙したものです。

(3) Pointerからuintptrへの変換と、その逆の変換(算術付き)。

p が割り当てられたオブジェクトを指している場合,uintptr への変換,オフセットの追加,Pointer への変換で,オブジェクトを進めることができます。

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

このパターンの最も一般的な使い方は、構造体のフィールドや配列の要素にアクセスすることです。

// equivalent to f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))

// equivalent to e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))

この方法では、ポインタにオフセットを加えることも、ポインタからオフセットを引くことも可能です。また、通常はアライメントのためにポインタを丸めるために&^を使用することも有効です。いずれの場合も、結果は割り当てられた元のオブジェクトを指し続けなければなりません。

C言語とは異なり、ポインターを元の割り当ての終わりを超えて進めることはできません。

// INVALID: end points outside allocated space.
var s thing
end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))

// INVALID: end points outside allocated space.
b := make([]byte, n)
end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

両方の変換は,それらの間に算術を介在させるだけで,同じ式の中に現れなければならないことに注意してください。

// INVALID: uintptr cannot be stored in variable
// before conversion back to Pointer.
u := uintptr(p)
p = unsafe.Pointer(u + offset)

なお、ポインタは割り当てられたオブジェクトを指していなければならないので、nilではないかもしれません。

// INVALID: conversion of nil pointer
u := unsafe.Pointer(nil)
p := unsafe.Pointer(uintptr(u) + offset)

(4) syscall.Syscallを呼び出す際のPointerからuintptrへの変換。

パッケージsyscallに含まれるSyscall関数は、そのuintptr引数をオペレーティングシステムに直接渡しますが、オペレーティングシステムは、呼び出しの詳細に応じて、そのうちのいくつかをポインタとして再解釈することがあります。つまり、システムコールの実装は、暗黙のうちに特定の引数をuintptrからポインタに変換し直しているのです。

ポインタの引数を引数として使うためにuintptrに変換しなければならない場合は、その変換は呼び出し式自体に現れなければなりません。

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

コンパイラは、アセンブリで実装された関数の呼び出しの引数リストにuintptrに変換されたPointerがある場合、型だけを見るとオブジェクトが呼び出し中に不要になったように見えても、参照された割り当てオブジェクトがあれば、呼び出しが完了するまで保持して移動しないように手配することで処理します。

コンパイラがこのパターンを認識するためには,引数リストに変換が現れなければならない。

// INVALID: uintptr cannot be stored in variable
// before implicit conversion back to Pointer during system call.
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

(5) reflect.Value.Pointerまたはreflect.Value.UnsafeAddrの結果のuintptrからPointerへの変換。

パッケージreflectのValueメソッドであるPointerとUnsafeAddrは、unsafe.Pointerの代わりにuintptr型を返すことで、呼び出し側が最初に "unsafe "をインポートせずに結果を任意の型に変更できないようにしています。しかし、これは結果が壊れやすいことを意味し、呼び出した直後に同じ式の中でPointerに変換する必要があります。

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

上記の場合と同様に,変換前の結果を保存することは無効です。

// INVALID: uintptr cannot be stored in variable
// before conversion back to Pointer.
u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))

(6) reflect.SliceHeaderまたはreflect.StringHeader DataフィールドのPointerへの変換、またはPointerからの変換。

前述のケースと同様に、reflectのデータ構造であるSliceHeaderとStringHeaderは、フィールドDataをuintptrとして宣言し、呼び出し側が最初に "unsafe "をインポートすることなく、結果を任意の型に変更できないようにしています。しかし、これはSliceHeaderとStringHeaderが実際のスライスや文字列の値の内容を解釈する場合にのみ有効であることを意味します。

var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1
hdr.Data = uintptr(unsafe.Pointer(p))              // case 6 (this case)
hdr.Len = n

この使い方では、hdr.Dataは、実際には、文字列ヘッダ内の基礎となるポインタを参照するための代替手段であり、uintptr変数そのものではありません。

一般的に、reflect.SliceHeaderとreflect.StringHeaderは、実際のスライスや文字列を指すreflect.SliceHeaderとreflect.StringHeaderとしてのみ使用されるべきであり、決して単純な構造体として使用されるべきではありません。プログラムでは、これらの構造体タイプの変数を宣言したり、割り当てたりしてはいけません。

// INVALID: a directly-declared header will not hold Data as a reference.
var hdr reflect.StringHeader
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n
s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost