|
| 1 | +package unsafes |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "unsafe" |
| 6 | +) |
| 7 | + |
| 8 | +// Add は、unsafe.Add関数を利用してポインタ演算するサンプルです。 |
| 9 | +// |
| 10 | +// unsafe.Addは、Go 1.17で追加された関数。 |
| 11 | +// それまでは unsafe.Pointer(uintptr(ptr) + uintptr(len)) という形で行っていたポインタ演算を |
| 12 | +// 内部で行ってくれるヘルパー関数。 |
| 13 | +// |
| 14 | +// これを利用することで Go でも ポインタ演算 出来るようになる。 |
| 15 | +// ただし、unsafeパッケージを利用する時点でGoの持つ安全性を無くすことに注意が必要。 |
| 16 | +// |
| 17 | +// REFERENCES: |
| 18 | +// - https://pkg.go.dev/unsafe@go1.25.3#Add |
| 19 | +func Add() error { |
| 20 | + var ( |
| 21 | + // 0x01020304 をリトルエンディアン環境で格納すると |
| 22 | + // メモリ上は [04][03][02][01] の順に配置される |
| 23 | + value int = 0x01020304 |
| 24 | + ptr = unsafe.Pointer(&value) |
| 25 | + bytePtr = (*byte)(ptr) |
| 26 | + sizeOfByte = unsafe.Sizeof((byte)(0)) |
| 27 | + ) |
| 28 | + |
| 29 | + // |
| 30 | + // --- byte単位でのアクセス (1バイトずつ読み取り) --- |
| 31 | + // |
| 32 | + |
| 33 | + // 先頭バイト(最下位バイト)を表示 |
| 34 | + // リトルエンディアンなので 0x04 が格納されている |
| 35 | + fmt.Printf("1バイト目: 0x%02X\n", *bytePtr) |
| 36 | + |
| 37 | + // アドレスを (1 * sizeof(byte)) 分進める (Go 1.17までのやり方) |
| 38 | + // uintptrに変換してからオフセット加算し、再度unsafe.Pointerに戻す |
| 39 | + // |
| 40 | + // (重要) |
| 41 | + // この方法で処理する場合は、ポインタ演算部分は1ステートメントで行うことが必須。 |
| 42 | + // uintptrは単なる整数型であり、GCはuintptrが指すオブジェクトを追跡しない。 |
| 43 | + // そのため、uintptr型の値を変数に保持したまま別の処理を挟むと、 |
| 44 | + // その間にGCが実行され、元のオブジェクトが移動または解放される可能性がある。 |
| 45 | + // |
| 46 | + // NG: |
| 47 | + // |
| 48 | + // tmp := uintptr(ptr) + offset // この時点でGCが入る可能性 |
| 49 | + // newPtr := unsafe.Pointer(tmp) // tmpが指すアドレスは既に無効かもしれない |
| 50 | + // |
| 51 | + // OK: |
| 52 | + // |
| 53 | + // newPtr := unsafe.Pointer(uintptr(ptr) + offset) // 1ステートメントで完結 |
| 54 | + // |
| 55 | + ptr = unsafe.Pointer(uintptr(ptr) + sizeOfByte) |
| 56 | + bytePtr = (*byte)(ptr) |
| 57 | + |
| 58 | + // 2バイト目を表示 (0x03) |
| 59 | + fmt.Printf("2バイト目: 0x%02X\n", *bytePtr) |
| 60 | + |
| 61 | + // アドレスを (2 * sizeof(byte)) 分進める (Go 1.17以降のやり方) |
| 62 | + // |
| 63 | + // unsafe.Add を使うことで、より簡潔にポインタ演算が可能 |
| 64 | + // (unsafe.Addは内部で1ステートメントとして処理される) |
| 65 | + // |
| 66 | + // 元の位置から2バイト進めるので、4バイト目(0x01)にアクセス |
| 67 | + ptr = unsafe.Add(ptr, uintptr(2*sizeOfByte)) |
| 68 | + bytePtr = (*byte)(ptr) |
| 69 | + |
| 70 | + // 4バイト目(最上位バイト)を表示 (0x01) |
| 71 | + fmt.Printf("4バイト目: 0x%02X\n", *bytePtr) |
| 72 | + |
| 73 | + // |
| 74 | + // --- int16単位でのアクセス (2バイトずつ読み取り) --- |
| 75 | + // キャスト先を *byte から *int16 へ変更 |
| 76 | + // |
| 77 | + |
| 78 | + var ( |
| 79 | + ptr2 = unsafe.Pointer(&value) // 先頭アドレスを再取得 |
| 80 | + shortPtr = (*int16)(ptr2) |
| 81 | + sizeOfShort = unsafe.Sizeof((int16(0))) |
| 82 | + ) |
| 83 | + |
| 84 | + // 最初の2バイト [04][03] をint16として読み取り |
| 85 | + // リトルエンディアンなので 0x0304 と解釈される |
| 86 | + fmt.Printf("short: 0x%04X\n", *shortPtr) |
| 87 | + |
| 88 | + // 2バイト(1 * sizeof(short))進めて、次の2バイト [02][01] をint16として読み取り |
| 89 | + // リトルエンディアンなので 0x0102 と解釈される |
| 90 | + shortPtr = (*int16)(unsafe.Add(ptr2, sizeOfShort)) |
| 91 | + fmt.Printf("short: 0x%04X\n", *shortPtr) |
| 92 | + |
| 93 | + return nil |
| 94 | + |
| 95 | + /* |
| 96 | + $ task |
| 97 | + task: [build] go build -o "/home/dev/dev/github/try-golang/try-golang" . |
| 98 | + task: [run] ./try-golang -onetime |
| 99 | +
|
| 100 | + ENTER EXAMPLE NAME: unsafe_add |
| 101 | +
|
| 102 | + [Name] "unsafe_add" |
| 103 | + 1バイト目: 0x04 |
| 104 | + 2バイト目: 0x03 |
| 105 | + 4バイト目: 0x01 |
| 106 | + short: 0x0304 |
| 107 | + short: 0x0102 |
| 108 | +
|
| 109 | + [Elapsed] 12.542µs |
| 110 | + */ |
| 111 | +} |
0 commit comments