Swift3のポインタの基礎知識

基本的にSwiftからポインタをそのまま扱う機会はほとんどないが、CのAPIやMetal等でポインタを扱う場合用のメモ

なお、画像などのバイナリデータを単に扱いたいだけの場合はDataUInt8の配列と同等に扱えるようになったのでそっちを使った方が良い

実際の使い方は実践編も参照

概要

ポインタはメモリ上のデータにアクセスする時に使うものである。 Swiftの場合、紐付けられているによって、以下の種類がある

〜BufferPointerは、メモリ上に要素(データ)が連続しているバッファの場合に利用出来るポインタで、実体はメモリへのビューである。 リリースモードでは境界チェックがされずにアクセスされる(デバッグモードではチェックされる)

注意事項

ポインタを使う場合、自動メモリ管理やアライメントの保証はされない。よってライフサイクルを自分で管理しメモリリークや未定義扱いとなる動作を自分で避けなければならない

〜RawPointerの場合は、タイプセーフではない (Rawがついていないものはコンパイラによる型チェックが行われるが、異なるデータ型を強制的に割り当てたような場合は当然未定義の動作となる)

ポインタの状態

ポインタはポインタの指すメモリの状態によって以下のような状態となる

trivial type

型の中に参照を含まないデータ型。単純なバイトのコピーだけで複製できる型のこと

UnsafePointer / UnsafeMutablePointer

読み取り

書き込み

UnsafeRawPointer / UnsafeMutableRawPointer

読み書きの際にはアライメントに注意すること

読み取り

書き込み

ポインタ演算

+-[](subscript)が利用可能。 それぞれ指定した数だけオフセット(※)されてアクセスされる

※UnsafePointer / UnsafeMutablePointerの場合は、その型のバイト数分だけ。UnsafeRawPointer / UnsafeMutableRawPointerの場合は、バイト単位でオフセットされる

// intPointerがIntの[0, 1, 2, 3, 4]のメモリを指している場合
intPointer + 2      // 2
intPointer[2]       // 2

型変換

互換性のある別の型に一時的にバインド

withMemoryRebound(to:capacity:)を使う

// 例:Int8 => UInt8
uint8Pointer.withMemoryRebound(to: Int8.self, capacity: 8) {
    // $0
}

別の型に永続的にバインド

  1. RawPointerにしてから指定の型へバインドしなおす

    // 例:UInt8 => UInt64
    let uint64Pointer = UnsafeRawPointer(uint8Pointer)
                            .bindMemory(to: UInt64.self, capacity: 1)
    

    なお、再バインドされるのでこの場合、元のuint8Pointerは未定義になる

  2. trivial types同士の場合
    RawPointerにしてからloadを使う

    let rawPointer = UnsafeRawPointer(uint64Pointer)
    fullInteger = rawPointer.load(as: UInt64.self)   // OK
    firstByte = rawPointer.load(as: UInt8.self)      // OK
    

    Mutableの場合はstoreBytes(of:toByteOffset:as:)で設定もできる

暗黙の変換とブリッジ

関数の引数に渡す時限定で暗黙的なキャストとブリッジをしてくれる

例えば、

func printInt(atAddress p: UnsafePointer<Int>)

というメソッドがある場合、

var value: Int = 23
printInt(atAddress: &value)         // &をつける

let numbers = [5, 10, 15, 20]       // 配列はlet
printInt(atAddress: numbers)        // 配列は&不要

とすれば、自動でUnsafePointerへ変換して処理される

もし、printIntの引数がUnsafeMutablePointerの場合であれば、

var value: Int = 23
printInt(atAddress: &value)         // &をつける

var numbers = [5, 10, 15, 20]       // mutableなのでvar
printInt(atAddress: &numbers)       // inoutなので&

とする

注意事項

この暗黙の変換によるポインタの有効範囲は該当の関数のスコープ内のみ。 よって関数からポインタを返してそれを利用するのは禁止(動作は未定義である)

特に以下のように初期化に使うのは禁止

var number = 5
let numberPointer = UnsafePointer<Int>(&number)
// numberPointerの動作は未定義