swift指針&內存管理-指針類型使用

為什么說指針不安全

  • 我們在創建一個對象的時候,是需要在堆上開辟內存空間的
    但是這個內存空間的聲明周期是有限的
    也就意味著如果使用指針指向這塊內存空間,當這塊內存空間的生命周期結束(引用計數為0),那么當前的指針就變成未定義的了

  • 創建的內存空間是有邊界的,通過指針訪問的內存空間超過已開辟內存空間的邊界,也就是訪問了一個未知的內存空間

  • 指針類型與內存的值類型不一致,也不安全,這一點參考 swift指針&內存管理-內存綁定

指針類型

Swift中的指針分為兩類 typed pointer(指定指針數據類型) & raw pointer(原生指針-未指定指針數據類型)

image.png

如果需要開辟一段連續的內存空間,就可以使用 unsafeBufferPointer<T>, 當然了unsafeMutableBufferPointer<T> 就是可變的

連續的原生內存空間 unsafeRawBufferPointer / unsafeMutableRawBufferPointer , 這種指針需要結合 指針內存綁定來使用

原始指針-rawPointer 的使用

如何使用 rawPointer 來存儲4個整型的數據

在存儲之前,先了解幾個概念

print("MemoryLayout<Int>.size = \(MemoryLayout<Int>.size)")
print("MemoryLayout<Int>.stride = \(MemoryLayout<Int>.stride)")
print("MemoryLayout<Int>.alignment = \(MemoryLayout<Int>.alignment)")
print("MemoryLayout<Int32>.size = \(MemoryLayout<Int32>.size)")
print("MemoryLayout<Int32>.stride = \(MemoryLayout<Int32>.stride)")
print("MemoryLayout<Int32>.alignment = \(MemoryLayout<Int32>.alignment)")
print("MemoryLayout<Int16>.size = \(MemoryLayout<Int16>.size)")
print("MemoryLayout<Int16>.stride = \(MemoryLayout<Int16>.stride)")
print("MemoryLayout<Int16>.alignment = \(MemoryLayout<Int16>.alignment)")

結果:

MemoryLayout<Int>.size = 8

MemoryLayout<Int>.stride = 8

MemoryLayout<Int>.alignment = 8

MemoryLayout<Int32>.size = 4

MemoryLayout<Int32>.stride = 4

MemoryLayout<Int32>.alignment = 4

MemoryLayout<Int16>.size = 2

MemoryLayout<Int16>.stride = 2

MemoryLayout<Int16>.alignment = 2

MemoryLayout 是用來測定內存的

stride是步長,也就是一段連續內存空間 指定類型指針的偏移最小單位

alignment是對齊字節,一段連續內存空間,指令讀取內存數據,都是標準化操作,不會出現第一個整型讀了8字節,下一個整型讀了4字節這樣

然后我們進行 4個整型的數據的存儲

首先開辟一塊內存空間

UnsafeMutableRawPointer.allocate(byteCount: Int, alignment: Int)

byteCount: 開辟內存空間的總的字節大小

alignment: 連續內存空間中 每一個整型數據的對齊大小

然后存儲 - UnsafeMutableRawPointer - storeBytes(of: T, as: T.Type)

of - 存儲的數據

as - 存儲的數據的類型

let mP = UnsafeMutableRawPointer.allocate(
    byteCount: 4 * MemoryLayout<Int>.size, 
    alignment: MemoryLayout<Int>.stride)
    
for i in 0..<4 {
    mP.storeBytes(of: i, as: Int.self)
}
// 取出
for i in 0..<4 {
    let mV = mP.load(as: Int.self)
    let mV = mP.load(fromByteOffset: i * MemoryLayout<Int>.stride, as: Int.self)
    print("mV ===> \(mV)")
}

結果

mV ===> 3

mV ===> 3

mV ===> 3

mV ===> 3

為什么不是 0, 1, 2, 3

這是因為 mP 指向 UnsafeMutableRawPointer.allocate 開辟出來的一段連續內存空間首地址

mP.load(as: Int.self) 循環里每次取的都是 從首地址處取出 數據,所以總是一樣的3

調整之后

let mP = UnsafeMutableRawPointer.allocate(
    byteCount: 4 * MemoryLayout<Int>.size, 
    alignment: MemoryLayout<Int>.stride)
    
for i in 0..<4 {
    mP.storeBytes(of: i, as: Int.self)
}
// 取出
for i in 0..<4 {
    let mV = mP.load(fromByteOffset: i * MemoryLayout<Int>.stride, as: Int.self)
    print("mV ===> \(mV)")
}

結果

mV ===> 3

mV ===> 0

mV ===> 16

mV ===> 0

這又是為何

因為 mP.storeBytes(of: i, as: Int.self) 每次也只是往 mP指向的連續內存空間的首地址里存儲,所以最后存儲的 3會覆蓋前面的幾次寫值

let mP = UnsafeMutableRawPointer.allocate(
    byteCount: 4 * MemoryLayout<Int>.size, 
    alignment: MemoryLayout<Int>.stride)
    
for i in 0..<4 {
    // 正解
    mP.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
}
// 取出
for i in 0..<4 {
    let mV = mP.load(fromByteOffset: i * MemoryLayout<Int>.stride, as: Int.self)
    print("mV ===> \(mV)")
}

結果

mV ===> 0

mV ===> 1

mV ===> 2

mV ===> 3

也可以直接 計算具體指針位置進行寫值,前提是必須知道指針的類型才可以

for i in 0..<4 {
    (mP + i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
}

size/stride/alignment的理解

情況一

struct IFLObject1 {
    var age: Int
    var gender: Bool
}

print("MemoryLayout<IFLObject1>.size = \(MemoryLayout<IFLObject1>.size)")
print("MemoryLayout<IFLObject1>.stride = \(MemoryLayout<IFLObject1>.stride)")
print("MemoryLayout<IFLObject1>.alignment = \(MemoryLayout<IFLObject1>.alignment)")

結果

MemoryLayout<IFLObject1>.size = 9

MemoryLayout<IFLObject1>.stride = 16

MemoryLayout<IFLObject1>.alignment = 8

image.png

情況二

class IFLobject2 {
    var age: Int = 0
    var gender: Bool = true
    var heigh: Double = 170
    var heigh1: Double = 170
    var heigh2: Double = 170
    var heigh3: Double = 170
}
print("MemoryLayout<IFLobject2>.size = \(MemoryLayout<IFLObject2>.size)")
print("MemoryLayout<IFLobject2>.stride = \(MemoryLayout<IFLObject2>.stride)")
print("MemoryLayout<IFLobject2>.alignment = \(MemoryLayout<IFLObject2>.alignment)")

結果

MemoryLayout<IFLobject2>.size = 8

MemoryLayout<IFLobject2>.stride = 8

MemoryLayout<IFLobject2>.alignment = 8

與結構體不同的是,struct屬于值類型,棧上開辟空間,class 堆上開辟內存空間,指針大小為8字節, 所以8字節對齊,步長也是8字節

泛型指針的使用

泛型指針相比原生指針來說,就是當前指針綁定到了具體的類型

泛型指針訪問過程中,并不是使用store load 方法進行存儲 取值操作,而是使用到泛型指針內置的變量pointee

var age = 10
var age1 = withUnsafePointer(to: &age) {
    $0.pointee + 1
}
print("age1 = \(age1)")

結果

age1 = 11

另一種情況

var age = 10
withUnsafePointer(to: &age) {
    $0.pointee += 1
}

這種情況下 指針 0 是不可變的,同時0指向的內容 $0.pointee也是不可變的, 如果要操作,調整如下

var age = 10
withUnsafeMutablePointer(to: &age) {
    $0.pointee += 1
}
print("age = \(age)")

結果

age = 11

還有一種方式直接分配內存

var age = 10
let tPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
tPtr.initialize(to: age)
print(tPtr.pointee)

結果

10

struct IFLObjStruct {
    var age: Int
    var height: Double
}
var tPtr = UnsafeMutablePointer<IFLObjStruct>.allocate(capacity: 5)
tPtr[0] = IFLObjStruct(age: 19, height: 170.0)
tPtr[1] = IFLObjStruct(age: 20, height: 171.0)
tPtr[2] = IFLObjStruct(age: 21, height: 172.0)
tPtr[3] = IFLObjStruct(age: 22, height: 173.0)
tPtr[4] = IFLObjStruct(age: 23, height: 174.0)

print(tPtr[4])

結果

IFLObjStruct(age: 23, height: 174.0)

還可以

struct IFLObjStruct {
    var age: Int
    var height: Double
}
var tPtr = UnsafeMutablePointer<IFLObjStruct>.allocate(capacity: 5)
tPtr[0] = IFLObjStruct(age: 19, height: 170.0)
tPtr[1] = IFLObjStruct(age: 20, height: 171.0)
tPtr[2] = IFLObjStruct(age: 21, height: 172.0)
tPtr[3] = IFLObjStruct(age: 22, height: 173.0)
tPtr[4] = IFLObjStruct(age: 23, height: 174.0)
tPtr.deinitialize(count: 5)
// 回收內存空間
tPtr.deallocate()

tPtr = UnsafeMutablePointer<IFLObjStruct>.allocate(capacity: 5)
for i in 0..<5 {
    tPtr.advanced(by: i).initialize(to: IFLObjStruct(age: 19 + i * 5, height: 170.0 + Double(i * 5)))
}
for i in 0..<5 {
    print(tPtr.advanced(by: i).pointee)
}

結果

IFLObjStruct(age: 19, height: 170.0)

IFLObjStruct(age: 24, height: 175.0)

IFLObjStruct(age: 29, height: 180.0)

IFLObjStruct(age: 34, height: 185.0)

IFLObjStruct(age: 39, height: 190.0)

注意:

tPtr.advanced by 參數 含義是 只需要標明移動多少個指針內存單位, 并不需要計算具體移動的內存塊字節大小,

因為 泛型指針已經 指明了當前內存 綁定的具體類型, 與原生指針 adviced by 參數有所區別

一般情況下,我們會在 defer 中,也就是當前程序運行完成之后, 執行 deinitialize 與 deallocate

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容