Swift 指针详解

723 阅读6分钟

MemoryLayout

MemoryLayout是Swift为结构体struct等一些需要获取具体内存空间大小定义的枚举。

主要的三个属性

/// 对齐单位, 结构中 bitWidth 最大的属性占用的内存字节数
public static var alignment: Int { get }
/// 结构在内存占用的真实字节大小
public static var size: Int { get }
/// stride 在 size 基础上需要满足 alignment 的整数倍的条件, 所以经常会比 size 大,在连续内存或者 [T] 数组时必须使用 stride 进行计算
public static var stride: Int { get }

func testMemoryLayout() {
    struct Test {
        let res1: Bool = false
        let res2: Int = 0
        let res3: Bool = false
//            let res4: Int = 0
    }
    print("\(MemoryLayout<Test>.size)") // 24
}

为什么要内存对齐

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

Swift 内存指针

分类

Swift结构体C描述
UnsafePointerconst T *T类型常量指针, 指针及其所指向的内存内容均不可变
UnsafeMutablePointerT *T类型变量指针, 指针及其所指向的内存内容均可变
UnsafeBufferPointerconst [T] *[T] 类型的指针变量, 指向一个数组的开始位置, 数组内容均不可变
UnsafeMutableBufferPointer[T] *[T] 类型的指针变量, 指向一个数组的开始位置, 数组内容可变
UnsafeRawPointerconst void *无类型指针, 可以指向任何类型, 相当于指向 uint8 *
UnsafeMutableRawPointervoid *无类型类型指针, 可以指向任何类型, 相当于指向 uint8 *
UnsafeBufferRawPointerconst [void] *相当于 const [uint8] *
UnsafeMutableRawBufferPointer[void] *相当于 [uint8] *
OpaquePointerStructType *指向c语言中的一些自定义类型,Swift中并未有相对应的类型

  • unsafe:不安全的,并不是真的不安全,大概是提示开发者少用。
  • Write Access:可写入, 可以想改指向的内存值。
  • Collection:像一个容器,可添加数据。
  • Strideable:指针可使用 advanced 函数移动, buffer 都无法进行移动,是因为 buffer 是进入内存的一个视图,并不拥有它引用的内存。
  • Typed:是否需要指定类型(范型)。

typed pointer(类型指针)

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

包含如下四个类型指针

  • UnsafePointer
  • UnsafeMutablePointer
  • UnsafeBufferPointer
  • UnsafeMutableBufferPointer

UnsafeMutablePointer

被UnsafeMutablePointe引用的内存有三种状态:

  • Not Allocated:内存没有被分配,这意味着这是一个 null 指针,或者是之前已经释放过

  • Allocated but not initialized:内存进行了分配,但是值还没有被初始化

  • Allocated and initialized:内存进行了分配,并且值已经被初始化

    allocate

// 绑定类型并分配内存 // allocate是类方法 // capacity: Int表示向系统申请 capacity 个数的对应泛型类型的内存 let pointer = UnsafeMutablePointer.allocate(capacity: MemoryLayout.stride) // let pointer = UnsafeMutablePointer.allocate(capacity: MemoryLayout.stride(ofValue: CCInfo()))


initialize

// 初始化, 对于Int, Float, Double这些基本数据类型,分配内存之后会有默认值0 pointer.initialize(to: 12) // pointer.pointee 为 12 // 赋值 pointer.pointee = 10

 
deinitialize

// 与 initialize: 配对使用的 deinitialize: 用来销毁指针指向的对象 // 回到初始化值之前,没有释放指针指向的内存,指针依旧指向之前的值 pointer.deinitialize(count: 1)


deallocate

// 与 allocate(capacity:) 对应的 deallocate() 用来释放之前申请的内存 pointer.deallocate()


 >注意其实在这里对于 Int 这样的在 C 中映射为 int 的 “平凡值” 来说,deinitialize 并不是必要的,因为这些值被分配在常量段上。但是对于像类的对象或者结构体实例来说,如果不保证初始化和摧毁配对的话,是会出现内存泄露的。所以没有特殊考虑的话,不论内存中到底是什么,保证 initialize: 和 deinitialize 配对会是一个好习惯。

### UnsafePointer
UnsafePointer 是不可变的,C 中 const 修饰的指针对应 UnsafePointer (最常见的应该就是 C 字符串的 const char * 了)。

 - UnsafePointer中的pointee属性只能get不能set。
- UnsafePointer中没有allocate方法。
 
### 初始化
可以由UnsafeMutablePointer、OpaquePointer或其他UnsafePointer创建一个UnsafePointer指针。其他与UnsafeMutablePointer类似。

//通过另一个变量指针初始化一个UnsafePointer常量指针 let pointer = UnsafeMutablePointer.allocate(capacity: MemoryLayout.stride) pointer.pointee = 20 let pointer1 = UnsafePointer.init(pointer) print(pointer1.pointee) // 20


### 将指针引用的内存作为不同的类型访问 withMemoryRebound 将内存临时重新绑定到其他类型

var int8: Int8 = 123 let int8Pointer = withUnsafePointer(to: int8, {$0}) /// capacity 表示 T 类型的数量, 总的字节数 = MemoryLayout.size * capacity。 int8Pointer.withMemoryRebound(to: UInt8.self, capacity: 1) { ptr in print(ptr.pointee) // 123 }


### bindMemory

该方法绑定内存为指定类型并返回一个UnsafeMutablePointer<指定类型>的指针,用到了指向内存的原始指针。

let intPointer = UnsafeRawPointer(int8Pointer).bindMemory(to: Int.self, capacity: 1) print(intPointer.pointee) // 123


在使用 bindMemory方法将原生指针绑定内存类型,转为类型指针的时候,一次只能绑定一个类型,例如:将一个原生指针绑定Int类型,不能再绑定Bool类型。

### UnsafeBufferPointer

UnsafeBufferPointer表示一组连续数据指针。BufferPointer实现了Collection,因此可以直接使用Collection中的各种方法来遍历操作数据,filtermap...,Buffer可以实现对一块连续存在空间进行操作,类似C中的数组的指针。
但是同样的,这个UnsafeBufferPointer是常量,它只能获取到数据,不能通过这个指针去修改数据。与之对应的是UnsafeMutableBufferPointer指针。

let array = [1, 2, 3, 4] // 遍历 let ptr = array.withUnsafeBufferPointer({$0}) ptr.forEach { element in print(element) // 1,2,3,4 }

//遍历 array.withUnsafeBufferPointer { ptr in ptr.forEach { print($0) // 1,2,3,4 } }


UnsafeBufferPointer 可以使用 baseAddress 属性,这个属性包含了缓冲区的基本地址。

let array: [Int8] = [65, 66, 67, 0] puts(array) // ABC array.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer) in puts(ptr.baseAddress! + 1) //BC }

let count = 2 let pointer = UnsafeMutablePointer.allocate(capacity: count) pointer.initialize(repeating: 0, count: count)

defer { pointer.deinitialize(count: count) pointer.deallocate() }

pointer.pointee = 42 // pointer 指向的内存地址存放数值 42 pointer.advanced(by: 1).pointee = 6 // pointer 下一个内存地址存放数值 6,即 pointer 指向的起始地址加 Int 类型的步幅再移动 1 位,就其起始地址 pointer.pointee pointer.pointee pointer.advanced(by: 1).pointee

let bufferPointer = UnsafeBufferPointer(start: pointer, count: count) for (index, value) in bufferPointer.enumerated() { print("value (index): (value)") // value 0: 42, value 1: 6 }

  
###  UnsafeMutableBufferPointer
可变的序列指针,UnsafeMutableBufferPointer拥有对指向序列修改的能力:

let pointer = UnsafeMutablePointer.allocate(capacity: count) let bufferPointer = UnsafeMutableBufferPointer.init(start: pointer, count: 5) // 拓展为5各元素的大小 bufferPointer[0] = 120 bufferPointer[1] = 130 //进行修改,其他未修改的内容将产生随机值 bufferPointer.forEach { (a) in print(a) // 120, 130, 120054000649232, 73, 105553129173888 } print(bufferPointer.count) // 5

状况跟UnsafeBufferPointer有点类似,只是在初始化的时候,需要借助UnsafeMutablePointer。 并不能直接使用已经存在序列进行初始化。
值的注意的是:如果一个序列被初始化之后,没有给每一个元素赋值的话,这些元素的值都将出现随机值


## raw pointer(原生指针)

- UnsafeRawPointer
- UnsafeMutableRawPointer
- UnsafeRawBufferPointer
- UnsafeMutableRawBufferPointer

### UnsafeMutableRawPointer
UnsafeMutableRawPointer 用于访问和操作非类型化数据的原始指针。

// 分配内存, byteCount: 表示总共需要的字节数, 表示 Int 类型的对齐方式 let pointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: MemoryLayout.alignment)

// 将给定值存储在指定偏移量的原始内存中 pointer.storeBytes(of: 0x00060001, as: UInt32.self)

// 从pointer引用的内存 用UInt8实例加载(即第一个字节用UInt8实例加载) let value = pointer.load(as: UInt8.self) print(value) // 1

// pointer.storeBytes(of: 42, as: Int.self) // let value = pointer.load(as: Int.self) // print(value) 42

let offsetPointer = pointer.advanced(by: 2) // let offsetPoint = pointer + 2 // 偏移 2个字节, 如果偏移3个字节,下面的操作就会越界了 let offsetValue = offsetPointer.load(as: UInt16.self) // 将第三个和第四个字节作为UInt16实例加载

print(offsetValue) // 6 pointer.deallocate()


注:方法 storeBytes 和 load 分别是用来存储和读取字节数的。

### UnsafeRawPointer

用于访问非类型化数据的原始指针。UnsafeRawPointer只能由其他指针用init方法得到,与UnsafePointer类似,没有allocate静态方法。但是,与UnsafeMutableRawPointer类似的有两种绑定方法bindMemory和assumingMemoryBound,绑定成UnsafePointer指针。

// 访问不同类型的相同内存 var uint64: UInt64 = 257 let rawPointer = UnsafeRawPointer(UnsafeMutablePointer(&uint64)) let int64PointerT = rawPointer.load(as: Int64.self) let uint8Point = rawPointer.load(as: UInt8.self)

print(int64PointerT) // 257 print(uint8Point) // 1

// 257 = 1 0000 0001 而UInt8 表示存储8个位的无符号整数,即一个字节大小, 2^8 = 256, [0, 255], 超出8个位范围的无法加载,所以打印为1

###  UnsafeRawBufferPointer 与 UnsafeMutableRawBufferPointer
  
  >UnsafeRawBufferPointer和UnsafeMutableRawBufferPointer 指代的是一系列的没有被绑定类型的内存区域。我们可以理解成他们实际上就是一些数组,再绑定内存之前,其中包含的元素则是每一个字节。
在底层,基本数据单元的复制是有效的,另外没有被 retain 和 stong 的也是能够安全的复制的,同样的,对于来自C API的对象也能够安全的复制。对于原声的Swift类型,有的包含了引用的对象的复制则有可能失败,但是我们可以使用指针对他们的值进行复制,这样的结果是有效的。如果我们强行对一下发类型进行复制,不一定有效,除非使用像C语言中的APImemmove().来操作

  >UnsafeRawBufferPointer和UnsafeMutableRawBufferPointer是内存视图,尽管我们知道它指向的内存区域,但是它并不拥有这块内存的引用。复制UnsafeRawBufferPointer 类型的变量不会复制它的内存;但是初始化一个集合到另一个新的集合过程会复制集合中的引用内存。

  ### 总结:

- 内存中的每个字节都被视为一个独立于内存绑定类型的 UInt8 值, 与该内存中保存的值的类型无关。
- UnsafeRawBufferPointer / UnsafeMutableRawBufferPointer 实例是内存区域中原始字节的视图。
- 通过原始缓冲区从内存中读取是一种无类型操作, UnsafeMutableRawBufferPointer 实例可以写入内存, UnsafeRawBufferPointer 实例不可以。
- 如果要类型化,必须将内存绑定到一个类型上。

let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: 3, alignment: MemoryLayout.alignment) pointer.copyBytes(from: [1, 2, 3]) pointer.forEach { print($0) // 1, 2, 3 }


Memory Access

要通过类型化操作访问底层内存,必须将内存绑定到一个简单的类型。

- withUnsafePointer
- withUnsafeMutablePointers
- withUnsafeBytes
- withUnsafeMutableBytes


## withUnsafePointer/withUnsafeMutablePointer
Swift 中不能像 C 里那样使用 & 符号直接获取地址来进行操作。如果我们想对某个变量进行指针操作,我们可以借助 withUnsafePointer 或 withUnsafeMutablePointer 这两个辅助方法。withUnsafePointer 或 withUnsafeMutablePointer 的差别是前者转化后的指针不可变,后者转化后的指针可变。

 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a3d410d8cad4cabaeda50a48e98ebe8~tplv-k3u1fbpfcp-zoom-1.image)

var a = 0

withUnsafePointer(to: &a) { ptr in print(ptr) // 0x00007ffeeccb3b40 }

a = withUnsafePointer(to: &a) { ptr in return ptr.pointee + 2 // 此时, 会新开辟空间, 令a指向新地址, 值为2, }

// 修改指针指向的内存值 var b = 42 withUnsafeMutablePointer(to: &b) { ptr in ptr.pointee += 100 // 未开辟新的内存空间, 直接修改a所指向的内存值 } print(b) // 142

var arr = [1, 2, 3] withUnsafeMutablePointer(to: &arr) { ptr in ptr.pointee[0] = 10 } print(arr) // [10, 2, 3]

arr.withUnsafeBufferPointer { ptr in ptr.forEach{ print("($0)") // 10 2 3 } }

// 修改内存值 arr.withUnsafeMutableBufferPointer { ptr in ptr[0] = 100

  ptr.forEach {
      print("\($0)") // 100 2 3
}

}


获取 struct 类型实例的指针

struct User { var name: Int = 5

  init(name: Int = 5) {
      self.name = name
  }

}

var user = User()

let pointer = withUnsafeMutablePointer(to: &user, {$0}) print(user) // user pointer.pointee = User(name: 10) print("(pointer.pointee)") // User(name: 10) print(user) // User(name: 10)


获取 class 类型实例的指针
获取 class 类型实例的指针和上面不同,不是使用withUnsafePointer 或 withUnsafeMutablePointer,而是使用下面讲到的Unmanaged,之所以放在这里,是想因为这里讲到获取对象指针,所以附带讲一下。

func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
  let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
  let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Self>.stride)
  return UnsafeMutablePointer<Int8>(mutableTypedPointer)

}


## withUnsafeBytes/withUnsafeMutableBytes

![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e332e7c55d83415fa435506f9e93f7f7~tplv-k3u1fbpfcp-zoom-1.image)

可以使用withUnsafeBytes/withUnsafeMutableBytes获取实例的字节数。

// 打印字符串 let string = "hello" let data = string.data(using: .ascii) data?.withUnsafeBytes{ (ptr: (UnsafePointer)) in print(ptr.pointee) // 104 = 'h' print(ptr.advanced(by: 1).pointee) // 101 = 'e' }

// 打印结构体 struct SampleStruct { let number: UInt32 let flag: Bool }

MemoryLayout.size // returns 5 MemoryLayout.alignment // returns 4 MemoryLayout.stride // returns 8

var sampleStruct = SampleStruct(number: 25, flag: true)

withUnsafeBytes(of: &sampleStruct) { bytes in for byte in bytes { print(byte) // 25 0 0 0 1 } }

let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in return bytes // 这里会有奇怪的bug! }

print("Horse is out of the barn!", bytes) // undefined !!!


 ### 注:
  
  - 不要从 withUnsafeBytes 中返回指针。
  - 绝对不要让指针逃出 withUnsafeBytes(of:) 的作用域范围。这样的代码会成为定时炸弹,你永远不知道它什么时候可以用,而什么时候会崩溃。