1. 程式人生 > >固定大小的陣列

固定大小的陣列

作者:Russ Bishop,原文連結,原文日期:2018-10-30 譯者:zhongWJ;校對:numbbbbbCee;定稿:Forelax

假設我們想要用 statfs() 方法來確定某個掛載點所對應的 BSD 裝置名。例如掛載點 /Volumes/MyDisk 對應的 BSD 裝置是 /dev/disk6s2

struct statfs fsinfo;
if (statfs(path, &fsinfo) != 0) {
    //錯誤
}
複製程式碼

同等的 Swift 程式碼如下,只不過多了個 POSIX 錯誤幫助方法:

func posix_expects_zero<R: BinaryInteger>(_
f: @autoclosure ()
throws -> R) throws { let returncode = try f() if returncode != 0 { // 如果需要,請在此處替換為自定義的錯誤型別。 // NSError 會自動幫我們通過錯誤碼得到對應的 C 字串錯誤訊息。 throw NSError( domain: NSPOSIXErrorDomain, code: numericCast(returncode), userInfo: nil
) } } // 採用預設的空初始化方法。Swift 能推斷出結構體型別, // 但為了表示得更清楚,這裡顯式指定型別 var fsinfo: statfs = statfs() statfs(path, &fsinfo) 複製程式碼

C 的引入物

statfs() 函式在 C 語言的定義是 int statfs(const char *path, struct statfs *info)statfs 結構體有多個欄位,但我們只關注 mount-from-name

struct statfs {
    //...
    char f_mntfromname[MAXPATHLEN];
    //...
} 複製程式碼

在蘋果平臺上 MAXPATHLEN == PATH_MAX == 1024。

如果你在程式碼裡硬編碼 1024 而不是用更合適的巨集,小心我的鬼魂會纏著你和你的家族十二代哦。

噢哦。一個固定大小的陣列。當被引入 Swift 中時,它會被當做一個有 1024 個元素的元組:

public var f_mntfromname: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
複製程式碼

這個型別不是很實用。那我們能不能做點什麼呢?由於這篇部落格的存在,你可能已經猜到答案是「」。

C 字串

這個固定大小的陣列包含了 char。由於文件未曾提及,所以我們並不知道是否通過空字元來表示陣列的終止。令人惱火的是,文件暗示在 64 位系統上,f_fstypename 欄位是由空字元終止,但對於 mount to/from 兩個欄位卻隻字未提。這兩個欄位是根據被定義為 PATH_MAXMAXPATHLEN 巨集來定義的而不是直接根據 PATH_MAX 巨集,而 PATH_MAX 巨集通常暗示陣列由空字元來表示終止。我們是不是應該從中得到一點啟發呢?

對於固定大小的陣列,有一些 C API 仍然採用空終止符(所以真實的字串長度最長可以是 sizeof(array) - 1),而另一些則樂意填充整個緩衝區(所以不以空字元結尾的字串長度最長可以是 sizeof(array))。這就是那種有害的絆腳石,它讓你的程式看起來執行正常並通過所有測試,然後碰到某些新的 FizzyWizz 硬碟系統會有的奇怪的邊界情況時,當突然碰到一個剛好由 1024 個字元組成的名稱特別長的 BSD 裝置時,結果就是你的程式出現了「可利用記憶體損壞」錯誤。

這些值很有可能是空字元終止的(也可能不是),但我會告訴你如何來處理這個問題,以便在兩種情況下都適用。這意味著我們再也不需要考慮這個問題,從而降低大腦負荷。在其他場景複用這個程式碼的人也不需要再考慮這個問題了。既然有這麼大的好處,何不馬上開始?

偏題了,讓我們回到正題……

部分解決方案

首先我們需要計算欄位的偏移量。用新的 MemoryLayout.offset 方法可以得到結果:MemoryLayout<statfs>.offset(of: \Darwin.statfs.f_mntfromname)!。由於結構體和函式名字相同,當我們構造關鍵路徑時,需要提供完整的路徑名(fully-qualified name),否則會得到「無法確定有歧義的引用路徑」的錯誤。我們可以強制解包返回值因為我們知道關鍵路徑有效並且欄位有偏移量。

將欄位的記憶體佈局偏移量加上 withUnsafePointer 指標就可以得到一個指向結構體欄位起始記憶體的指標。我們可以通過這種方式建立一個字串物件:

return withUnsafePointer(to: fsinfo, { (ptr) -> String? in
    let offset = MemoryLayout<statfs>.offset(of: \Darwin.statfs.f_mntfromname)!
    let fieldPtr = (UnsafeRawPointer(ptr) + offset).assumingMemoryBound(to: UInt8.self)
    if fieldPtr[count - 1] != 0 {
        let data = Data(bytes: UnsafeRawPointer(fieldPtr), count: count)
        return String(data: data, encoding: .utf8)
    } else {
        return String(cString: fieldPtr)
    }
})
複製程式碼

我們首先快速檢查了緩衝區是否是空字元結尾。如果是,就採用 C 字串這條捷徑。反之,為了便於使用 String 的長度限制構造方法,我們建立了一個 Data 例項。看起來用 Data(bytesNoCopy:count:deallocator:) 也行,但 String(data:encoding:) 初始化方法並不保證拷貝 Data 底層的緩衝區,Data 的構造過程也同樣如此。雖然這種情況極少見,我們還是謹慎為好。(假如目前的方法會導致效能問題,我可能會花時間調研其他方案。)

有一種可能情況是,先寫入由空字元終止的較短的字串到緩衝區,剩餘的部分則是被垃圾資料填充。由於 Swift 在初始化結構體時會強制清除記憶體,所以只有當核心拷貝垃圾資料到這塊地址時,上述情況才可能發生。我們可以忽略這種情況,因為核心會盡量避免洩漏核心記憶體到使用者空間,否則我們就只能用更耗時的方式了。(將這些位元組轉換為字串的方式數不勝數,我這裡就不一一列舉了。)

現在我們來實現 statfs 的擴充套件:

extension statfs {
    var mntfromname: String? {
        mutating get {
            return withUnsafePointer(to: fsinfo, { (ptr) -> String? in
                let offset = MemoryLayout<statfs>.offset(of: \Darwin.statfs.f_mntfromname)!
                let fieldPtr = (UnsafeRawPointer(ptr) + offset).assumingMemoryBound(to: UInt8.self)
                let count = Int(MAXPATHLEN)
                if fieldPtr[count - 1] != 0 {
                    let data = Data(bytes: UnsafeRawPointer(fieldPtr), count: count)
                    return String(data: data, encoding: .utf8)
                } else {
                    return String(cString: fieldPtr)
                }
            })
        }
    }
}
複製程式碼

這段程式碼很管用,但假如我們也想處理別的欄位,例如 f_mntoname 呢?複製程式碼似乎不怎麼好,所以讓這段程式碼支援泛型,使之更加通用才對;我們只需要接受 key path 和 count 作為引數,再稍作修改就可以了:

func fixedArrayToString<T>(t: T, keyPath: PartialKeyPath<T>, count: Int) -> String? {
    return withUnsafePointer(to: t) { (ptr) -> String? in
        let offset = MemoryLayout<T>.offset(of: keyPath)!
        let fieldPtr = (UnsafeRawPointer(ptr) + offset).assumingMemoryBound(to: UInt8.self)
        if fieldPtr[count - 1] != 0 {
            let data = Data(bytes: UnsafeRawPointer(fieldPtr), count: count)
            return String(data: data, encoding: .utf8)
        } else {
            return String(cString: fieldPtr)
        }
    }
}

extension statfs {
    var mntfromname: String? {
        get {
            return fixedArrayToString(
                t: self,
                keyPath: \Darwin.statfs.f_mntfromname,
                count: Int(MAXPATHLEN))
        }
    }

    var mntonname: String? {
        get {
            return fixedArrayToString(
                t: self,
                keyPath: \Darwin.statfs.f_mntonname,
                count: Int(MAXPATHLEN))
        }
    }
}
複製程式碼

結語

現在你知道怎麼把有 N 個元素的元組變成更實用的東西了吧。

本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 swift.gg