下一站“內(nèi)存”:成為《黑客帝國》中的尼奧并不難
本文轉(zhuǎn)載自公眾號“讀芯術(shù)”(ID:AI_Discovery)
你有沒有想過,尼奧深陷“母體”時是如何設(shè)法改變它的?
他又是如何把子彈從崔妮蒂身上清除的?顯然,“母體”只是機(jī)器編寫的一個程序,尼奧能在程序運(yùn)行時更改程序的二進(jìn)制代碼,并在矩陣中交換子彈的位置。
如果說,你們也可以這么做,也可以成為你程序中的尼奧,你會作何感想?我的意思是,或許各位很難與電影里的尼奧相匹敵,不過也差不多了。
程序運(yùn)行過程中如何訪問和更改內(nèi)存?使用Swift的不安全API就可以做到。
什么是不安全?
Swift是一種內(nèi)存安全語言。它限制用戶直接訪問內(nèi)存,確保用戶在使用內(nèi)存前已初始化所有內(nèi)容。不安全的Swift API允許用戶通過指示器直接訪問內(nèi)存。
或許不安全這個詞聽起來很糟糕,不過它并不意味著用戶代碼處于危險狀態(tài)且無法正常運(yùn)行。Swift可以確保用戶不犯明顯錯誤。而使用不安全的API時,用戶必須時刻注意代碼的運(yùn)行情況。尤其是在使用C、C++等語言時,這些API十分有用。
圖源:unsplash
在弄清楚什么是不安全的Swift之前,需要先弄清楚什么是安全。
什么是內(nèi)存安全?
想弄清楚這種情況,先來看幾個例子。
例1:使用年齡數(shù)組,嘗試在數(shù)組的第一個元素中加1。
可以看到,這會出現(xiàn)錯誤,該值應(yīng)與前面字符隔開。繼續(xù)嘗試。
這樣好像可以了。如果用空數(shù)組再試一次呢?
它崩潰了......再試試別的。
例2:嘗試查找年齡數(shù)組的平均值。

它運(yùn)行良好,就好像被施了魔法。不過空數(shù)組也能行嗎?

它又崩潰了......這次我們將試著訪問數(shù)組中的元素。
例3:嘗試訪問數(shù)組第3個和第4個索引處的元素。
訪問第3個索引時,它可以正常運(yùn)行,但訪問第4個索引時,它又雙崩潰了。
很明顯,如果嘗試任何異常操作,那么程序每次都會崩潰。如果崩潰是安全的,那......什么是不安全的?
想一下,假如你嘗試訪問數(shù)組中的年齡,而程序返回了一個負(fù)值,這種情況是不可能同時發(fā)生吧?可如果你嘗試獲取賬戶余額,程序返回的值是1000,而實際余額有2000,那該怎么辦?
沒錯,意外行為要危險得多。Swift提供了安全的API,從而讓用戶避免意外行為。深入了解不安全的API之前,先來看看內(nèi)存和內(nèi)存布局。
什么是內(nèi)存?
在計算機(jī)中,內(nèi)存以數(shù)字形式存儲,比如許多的“1”和“0”,我們稱之為比特。如果將這樣的內(nèi)存可視化,會得到下面的圖像。
二進(jìn)制代碼
上圖呈現(xiàn)的是連續(xù)的比特流,代表實際數(shù)據(jù)。如果將每8個比特分為一組,那么這些比特組就是字節(jié)。如果將這些字節(jié)可視化,它們將如下圖所示。
字節(jié)代碼
為便于理解,把它們轉(zhuǎn)換成十六進(jìn)制代碼。
十六進(jìn)制代碼
如果繼續(xù)將每8個十六進(jìn)制代碼分為一組,就會得到8字節(jié)或者是64比特的字。這也是當(dāng)今全球使用的通用格式,構(gòu)成了大部分設(shè)備的“64位系統(tǒng)”。
字(64比特)
每個字都關(guān)聯(lián)一個地址,該地址也是十六進(jìn)制數(shù)。每個內(nèi)存地址之間都存在8個字節(jié)的差值,該差值剛好等于字的大小。該地址可用于訪問內(nèi)存中該點(diǎn)的數(shù)據(jù)。
帶有內(nèi)存地址的字
什么是內(nèi)存布局?
這是一個Swift API,可在運(yùn)行時告知用戶所提供類型的大小、對齊方式和跨度。
- 大?。涸擃愋退璧淖止?jié)數(shù)。
- 對齊方式:內(nèi)存應(yīng)是對齊方式的倍數(shù)。
- 跨度:兩個元素之間的距離。
內(nèi)存布局Swift
嘗試一些代碼,以進(jìn)一步了解內(nèi)存布局API。這些是在64位操作系統(tǒng)計算機(jī)上運(yùn)行該代碼所得到的值。
- MemoryLayout<Int>.size // returns 8
- MemoryLayout<Int>.alignment // returns 8
- MemoryLayout<Int>.stride // returns 8
- MemoryLayout<Bool>.size // returns 1
- MemoryLayout<Bool>.alignment // returns 1
- MemoryLayout<Bool>.stride // returns 1
- MemoryLayout<Double>.size // returns 8
- MemoryLayout<Double>.alignment // returns 8
- MemoryLayout<Double>.stride // returns 8
什么是不安全的指示器?
不安全的指示器是Swift API的其中一種,它允許用戶訪問流中的數(shù)據(jù)或?qū)?shù)據(jù)與特定類型(如Int、Double等)綁定。與直接內(nèi)存一起使用的類型,獲取“不安全”前綴。
Swift提供了8種類型的不安全指示器API,可根據(jù)實現(xiàn)特定目標(biāo)的需要進(jìn)行使用。
- UnsafePointer
- UnsafeMutablePointer
- UnsafeRawPointer
- UnsafeMutableRawPointer
- UnsafeBufferPointer
- UnsafeMutableBufferPointer
- UnsafeRawBufferPointer
- UnsafeMutableRawBufferPointer
為了更好地理解,來看一些例子。
例1:原始指示器
- let count = 2
- let stride = MemoryLayout<Int>.stride
- let alignment = MemoryLayout<Int>.alignment
- let byteCount = stride * count //total number of byteslet pointer =UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment:alignment)defer {
- pointer.deallocate()
- }pointer.storeBytes(of: 30, as: Int.self
- pointer.advanced(by: stride).storeBytes(of: 3, as: Int.self)
- pointer.load(as: Int.self)
- pointer.advanced(by: stride).load(as: Int.self)let bufferPointer =UnsafeRawBufferPointer(start: pointer, count: byteCount)for (index, byte) inbufferPointer.enumerated() {
- print("byte \(index) ->\(byte)")
- }// byte 0 -> 30
- // byte 1 -> 0
- // byte 2 -> 0
- // byte 3 -> 0
- // byte 4 -> 0
- // byte 5 -> 0
- // byte 6 -> 0
- // byte 7 -> 0
- // byte 8 -> 3
- // byte 9 -> 0
- // byte 10 -> 0
- // byte 11 -> 0
- // byte 12 -> 0
- // byte 13 -> 0
- // byte 14 -> 0
- // byte 15 -> 0
- advanced用于按提供的跨度移動指示器。
- UnsafeMutableRawPointer.allocate通過分配所需的類型返回可變的指示器。
- UnsafeRawBufferPointer讓用戶以字節(jié)集合的方式訪問內(nèi)存。用戶可對其進(jìn)行迭代編輯來訪問字節(jié)。
- storeByte會將提供的字節(jié)存儲在指定內(nèi)存中,而load將通過與特定類型(此處為Int)綁定來加載數(shù)據(jù)。
- ARC無法使用該API,用戶必須自行重新分配,因此,需要延遲代碼塊。每當(dāng)指令從當(dāng)前代碼塊返回時,它都將重新分配指示器。
例2:類型化的指示器
- let count = 2
- let stride = MemoryLayout<Int>.stridelet pointer =UnsafeMutablePointer<Int>.allocate(capacity: count)
- pointer.initialize(repeating: 0, count: count)defer {
- pointer.deinitialize(count: count)
- pointer.deallocate()
- }pointer.pointee = 42
- pointer.advanced(by: 1).pointee = 6let bufferPointer =UnsafeBufferPointer(start: pointer, count: count)for (index, value) inbufferPointer.enumerated() {
- print("value \(index) ->\(value)")
- }// value 0 -> 42
- // value 1 -> 6
- UnsafeMutablePointer
.allocate為提供的計數(shù)分配T類型所需的字節(jié)數(shù)。 - initialize將使用提供的值初始化指示器。
- pointee可用于存儲、加載T類型的值。
- advanced將指示器移至下一個字節(jié)。
不要做什么?
使用不安全的API時:
- 一次只綁定一種類型(嘗試臨時綁定)
- let count = 3
- let stride = MemoryLayout<Int16>.stride
- let alignment = MemoryLayout<Int16>.alignment
- let byteCount = count * stridelet pointer = UnsafeMutableRawPointer.allocate(
- byteCount: byteCount,
- alignment: alignment)let typedPointer1= pointer.bindMemory(to: UInt16.self, capacity: count)// 911, someone isbreaking the Law
- let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)//Try this way instead
- typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) {
- (boolPointer:UnsafeMutablePointer<Bool>) in
- print(boolPointer.pointee)
- }
- struct ExampleStruct {
- let number: Int
- let flag: Bool
- }var exampleStruct = ExampleStruct(number: 25, flag: true)let bytes = withUnsafeBytes(of:&exampleStruct) { bytes in
- return bytes // It may cause strangebugs anytime
- }print("Here are are bytes to ruin your life", bytes)
- let count = 3
- let stride = MemoryLayout<Int16>.stride
- let alignment = MemoryLayout<Int16>.alignment
- let byteCount = count * strideletpointer = UnsafeMutableRawPointer.allocate(
- byteCount: byteCount,
- alignment: alignment)let bufferPointer= UnsafeRawBufferPointer(start: pointer, count: byteCount + 1)
- // Putting it intentionally to cause an issue :pfor byte in bufferPointer {
- print(byte) // Check each byte
- }
學(xué)會這一招,快去你的程序中“遨游”吧!