UnsafePointer

swift 的記憶體存取

傳統的記憶體

相信指標對習慣於 C 語言的設計師來說並不陌生。舉一個最簡單的例子:C 語言並沒有提供原生字串的型態,所謂字串實際上就是連續不間斷的 char,並且使用指標指向該記憶體位置的最前端。C 語言習慣於使用指標達到三個主要的目的:

  1. 用來動態產生某個物件的實體
  2. 用來產生一連串相同大小的物件
  3. 分配一塊記憶體空間作為存取資料的緩衝區

C 語言提供了指標讓開發者可以簡單的存取記憶體的空間,但是這個方法卻也成為了產品不穩定,甚至出現安全性弱點的最主要元兇。

Swift 的記憶體存取

作為一門以安全性為設計出發點的程式語言,Swift 在大多數實作上面已經鮮少直接存取記憶體。但是在某些應用上面,依然會有機會去直接存取到記憶體的內容。好比如說:檔案的讀取、串流資料的處理等。

Swift 使用 UnsafePointer 系列物件來包裝 C 語言的指標,大多數的記憶體操作都必須要經過這個物件來操作。包裝之後的 UnsafePointer 相對比 C 指標來的安全,但是,如其物件名稱一般,直接使用指標來操作記憶體依然是具有風險的一件事情。下面我們就來介紹 UnsafePointer 的使用。

UnsafePointer 的狀態

Swift UnsafePointer 主要有兩種狀態:

  1. 已經分配 (allocated):
    C 語言建立指標之後必須與系統要求一塊記憶體空間,並且讓指標指向該記憶體位置,如下:

    void *ptr = malloc(size);

    正如 C 語言一般,單純建立一個 UnsafePointer 物件並不能直接拿來使用。Swift 強制在產生 UnsafePointer 物件的時候必須分配記憶體空間:

    var ptr = UnsafeMutablePointer<Int>.allocate(capacity: count)

    在這裡,我們產生了一個 Pointer 物件叫做 UnsafeMutablePointer,該物件是 UnsafePointer 的一種變體,代表一個可以修改內部值的指標。該指標指向可以存放 countInt 型別的記憶體空間。經過這個步驟之後,我們稱這個指標已經經過分配,並且已經處於可以寫入的狀態。

    與 C 語言相同的,經過記憶體分配之後的指標必須要手動釋放記憶體空間,一行簡單的 method 便可以達到這個目的:

    ptr.deallocate()

    當此 method 被執行之後,該指標物件便處於尚未分配的狀態,自然程式設計師也不能夠再度對該指標寫入任何東西。由於分配記憶體必須要自己手動做釋放的動作,為了避免粗心忘記,可以使用 swift 的 defer 敘述句保證該記憶體空間一定會被釋放。
  2. 已經初始化 (initialized):
    swift 包裝過指標之後,為了保證已經分配的記憶體空間在使用之前被確切的初始化過,只有經過分配的指標是處在「尚未初始化 (uninitialized)」的狀態,也就是說,程式上你並不能存取該指標的 pointee

    ptr.pointee = 10ptr[0] = 10

    如此一來,該指標便處於已經初始化的狀態,並且可以正常使用。有趣的是,UnsafeMutablePointer 允許你將已初始化的指標做反向操作,讓該指標成為尚未初始化的狀態:

    ptr.deinitialize()

我們上面使用 C 語言的概念講解了 swift 記憶體存取與狀態的機制,UnsafePointer 有許多概念是習慣使用 C 語言做程式設計的人應該能夠很容易的就理解該機制的意涵。

UnsafePointer 的各種變體

上面使用到了 UnsafeMutablePointer 作為一個可以改動內容值的指標。其實 UnsafeMutablePointer 只是UnsafePointer 的其中一種變體。swift 為了對應各種使用情境,分別設計了幾種不同的指標變體,分別是:

  • UnsafePointer:標準的指標,指向某種型態的記憶體區塊。
  • UnsafeMutablePointer:同 UnsafePointer,但是可以修改該記憶體區塊的內容。
  • UnsafeBufferPointer:指向某個型態的記憶體區塊,但是並不擁有該記憶體的所有權。
  • UnsafeMutableBufferPointer:同 UnsafeBufferPointer,但是可以修改記憶體內容。
  • UnsafeRawPointer:指向一塊記憶體區塊,但是不指定型態,類似 C 語言中的 void *
  • UnsafeMutableRawPointer:同UnsafeRawPointer,可以修改記憶體內容。

UnsafePointer 內部型態的轉換

由上面敘述我們可以知道,除了 UnsafeRawPointer 之外,所有的指標物件都必須要指定記憶體所屬的型態為何。雖然 swift 強制要求在設計程式的時候指定指標的型態,但還是有提供了 withMemoryRebound 方法來將其內部記憶體轉換成另外一種型態存取。好比如說,我們現在想要將 uint8 作為 int8 的記憶體來存取值得時候:

let length = uint8Pointer.withMemoryRebound(to: Int8.self, capacity: 8) {
return strlen($0)
}

上述方法可以讓程式設計師暫時地將指標轉換成另外一種型態。但是如果我們想要將指標永久轉換,那我們就必須要透過 UnsafeRawPointerbindMemory 來完成。

// 1 - 將指標轉換為 UnsafeRawPointer 物件
let rawPointer = UnsafeRawPointer(uint8Ptr)
// 2 - 取得新的指標
let int8Pointer = rawPointer.bindMemory(to: Int8.self, cacacity: 1)

總結

這篇文章簡單的介紹了 UnsafePointer 與一些基本的操作,指標概念源自於 C 語言,其中有許多概念是相合的。如果已經懂的如何操作 C 指標的話應該不會有太多的難處。但其實有許多地方是文章裡面沒有提起,卻是在實務撰寫程式上面相當重要的部分,可以參考蘋果開發者網站上面的資訊。

Manual Memory Management
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.