为什么说指针不安全

  • 我们在创立一个目标的时候,是需求在堆上拓荒内存空间的 可是这个内存空间的声明周期是有限的 也就意味着假如运用指针指向这块内存空间,当这块内存空间的生命周期结束(引用计数为0),那么当时的指针就变成未定义的了

  • 创立的内存空间是有鸿沟的,通过指针拜访的内存空间超过已拓荒内存空间的鸿沟,也便是拜访了一个不知道的内存空间

  • 指针类型与内存的值类型不一致,也不安全,这一点参考 swift指针&内存管理-内存绑定

指针类型

Swift中的指针分为两类 typed pointer(指定指针数据类型) & raw pointer(原生指针-未指定指针数据类型)

swift指针&内存管理-指针类型使用

假如需求拓荒一段接连的内存空间,就能够运用 unsafeBufferPointer, 当然了unsafeMutableBufferPointer 便是可变的

接连的原生内存空间 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.size = 8

MemoryLayout.stride = 8

MemoryLayout.alignment = 8

MemoryLayout.size = 4

MemoryLayout.stride = 4

MemoryLayout.alignment = 4

MemoryLayout.size = 2

MemoryLayout.stride = 2

MemoryLayout.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.size = 9

MemoryLayout.stride = 16

MemoryLayout.alignment = 8

swift指针&内存管理-指针类型使用

状况二

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.size = 8

MemoryLayout.stride = 8

MemoryLayout.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指向的内容 $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