swift内存管理指针类型使用实例详解
i_erlich 人气:0为什么说指针不安全
- 我们在创建一个对象的时候,是需要在堆上开辟内存空间的 但是这个内存空间的声明周期是有限的 也就意味着如果使用指针指向这块内存空间,当这块内存空间的生命周期结束(引用计数为0),那么当前的指针就变成未定义的了
- 创建的内存空间是有边界的,通过指针访问的内存空间超过已开辟内存空间的边界,也就是访问了一个未知的内存空间
- 指针类型与内存的值类型不一致,也不安全,这一点参考 swift指针&内存管理-内存绑定
指针类型
Swift中的指针分为两类 typed pointer(指定指针数据类型) & raw pointer(原生指针-未指定指针数据类型)
如果需要开辟一段连续的内存空间,就可以使用 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
情况二
class IFLobject2 { var age: Int = 0 var gender: Bool = true var heigh: Double = 170 var heigh2: Double = 170 var heigh3: Double = 170 var heigh4: 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指向的内容 $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
加载全部内容