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


    为什么说指针不安全

    • 我们在创建一个对象的时候,是需要在堆上开辟内存空间的
      但是这个内存空间的声明周期是有限的
      也就意味着如果使用指针指向这块内存空间,当这块内存空间的生命周期结束(引用计数为0),那么当前的指针就变成未定义的了

    • 创建的内存空间是有边界的,通过指针访问的内存空间超过已开辟内存空间的边界,也就是访问了一个未知的内存空间

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

    指针类型

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

    在这里插入图片描述

    如果需要开辟一段连续的内存空间,就可以使用 unsafeBufferPointer, 当然了unsafeMutableBufferPointer 就是可变的

    连续的原生内存空间 unsafeRawBufferPointer / unsafeMutableRawBufferPointer , 这种指针需要结合 指针内存绑定来使用

    原始指针-rawPointer 的使用

    如何使用 rawPointer 来存储4个整型的数据

    在存储之前,先了解几个概念

    print("MemoryLayout.size = \(MemoryLayout.size)")
    print("MemoryLayout.stride = \(MemoryLayout.stride)")
    print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
    print("MemoryLayout.size = \(MemoryLayout.size)")
    print("MemoryLayout.stride = \(MemoryLayout.stride)")
    print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
    print("MemoryLayout.size = \(MemoryLayout.size)")
    print("MemoryLayout.stride = \(MemoryLayout.stride)")
    print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果:

    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.size, 
    	alignment: MemoryLayout.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.stride, as: Int.self)
        print("mV ===> \(mV)")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果

    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.size, 
    	alignment: MemoryLayout.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.stride, as: Int.self)
        print("mV ===> \(mV)")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结果

    mV ===> 3

    mV ===> 0

    mV ===> 16

    mV ===> 0

    这又是为何

    因为 mP.storeBytes(of: i, as: Int.self) 每次也只是往 mP指向的连续内存空间的首地址里存储,所以最后存储的 3会覆盖前面的几次写值

    let mP = UnsafeMutableRawPointer.allocate(
    	byteCount: 4 * MemoryLayout.size, 
    	alignment: MemoryLayout.stride)
    	
    for i in 0..<4 {
    	// 正解
        mP.advanced(by: i * MemoryLayout.stride).storeBytes(of: i, as: Int.self)
    }
    // 取出
    for i in 0..<4 {
        let mV = mP.load(fromByteOffset: i * MemoryLayout.stride, as: Int.self)
        print("mV ===> \(mV)")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果

    mV ===> 0

    mV ===> 1

    mV ===> 2

    mV ===> 3

    也可以直接 计算具体指针位置进行写值,前提是必须知道指针的类型才可以

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

    size/stride/alignment的理解

    情况一

    struct IFLObject1 {
        var age: Int
        var gender: Bool
    }
    
    print("MemoryLayout.size = \(MemoryLayout.size)")
    print("MemoryLayout.stride = \(MemoryLayout.stride)")
    print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结果

    MemoryLayout.size = 9

    MemoryLayout.stride = 16

    MemoryLayout.alignment = 8

    在这里插入图片描述
    alignment 对齐是内存中布局的一种方式

    小单位 按照大单位去补齐, 如果凑不齐,就空余一定的空间出来,看上去就跟集中箱一样,遵循标准化原则

    age占8字节 + gender占1字节, size 大小就是9字节

    size 9字节 不是 age大小8字节的倍数, 空闲一部分空间,凑齐8字节整数倍,stride步长就是16字节

    stride 16字节 凑齐的方式就是按照 age8字节大小去对齐,所以 alignment 就是8字节对齐

    情况二

    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.size = \(MemoryLayout.size)")
    print("MemoryLayout.stride = \(MemoryLayout.stride)")
    print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结果

    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)")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结果

    age1 = 11

    另一种情况

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

    这种情况下 指针 $0 是不可变的,同时$0指向的内容 $0.pointee也是不可变的, 如果要操作,调整如下

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

    结果

    age = 11

    还有一种方式直接分配内存

    var age = 10
    let tPtr = UnsafeMutablePointer.allocate(capacity: 1)
    tPtr.initialize(to: age)
    print(tPtr.pointee)
    
    • 1
    • 2
    • 3
    • 4

    结果

    10

    struct IFLObjStruct {
        var age: Int
        var height: Double
    }
    var tPtr = UnsafeMutablePointer.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])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结果

    IFLObjStruct(age: 23, height: 174.0)

    还可以

    struct IFLObjStruct {
        var age: Int
        var height: Double
    }
    var tPtr = UnsafeMutablePointer.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.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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    结果

    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

  • 相关阅读:
    面试题-5
    【二叉树的顺序结构:堆 && 堆排序 && TopK]
    Kubernetes 笔记 / 入门 / 生产环境 / 用部署工具安装 Kubernetes / 用 kubeadm 启动集群 / 两种高可用拓扑
    腾讯云4核8G服务器CVM S5性能测评及优惠价格表
    Stable Diffusion stable-diffusion-webui ControlNet Lora
    「计算机基础」进制转换
    GaussDB(DWS)云原生数仓技术解析:湖仓一体,体验与大数据互联互通
    GSCoolink GSV9001 HDMI2to1 Video Processor
    吞吐量和IOPS测试
    <能力清单>笔记与思考
  • 原文地址:https://blog.csdn.net/qq_42431419/article/details/128005336