Swift对C指针实现浅析

个人认为,Swift对指针的处理略显复杂。

我们通过调试可以看到存在这样一个类型Builtin.RawPointer,我们可以假设其为C指针在Swife中的内部表示,但不可以直接使用。相反,在Swift中,定义了7种指针类型,可以通过这7种类型来操作C指针。

COpaquePointer

在C语言中,常使用typedef来定义一些指针类型的别名,如

1
typedef struct stack *stack_t

该定义表示一个指向栈结构的指针,但并没有给出结构的任何信息,我们不知道该结构都有哪些成员。类似于stack_t这样的指针就是一个不透明的指针。在程序中,我们可以自由的操纵这种指针,但无法反引用以获取指针的内部信息,只有接口的实现才有这种特权。这所以使用不透明指针,在于其隐藏了具体的实现细节,有助于捕获错误。即只能传相同的参数给函数,否则将产一变异错误。(参看《C语言接口与实现:创建可重用软件的技术》)

在Swift中,针对这种不透明指针,定义了一个包装器:COpaquePointer。该类型(实际为结构体)是一个不透明指针的包装器,主要用于Bridge Header中表示C中的复杂结构指针,当我们的指针指向的类型无法在Swift中有效地表示出来时,就可以使用该类型,而如果在Swift能找到对应的类型表示指针指向的类型,则可以使用UnsafePointer。

该类的实现及其扩展实现了四个协议,我们看看其具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct COpaquePointer : Equatable, Hashable, LogicValue {
init()
static func null() -> COpaquePointer
func getLogicValue() -> Bool
var hashValue: Int { get }
}
extension COpaquePointer {
/// 将UnsafePointer类型转换为不透明C指针
init<T>(_ from: UnsafePointer<T>)
}
extension COpaquePointer : CVarArg {
func encode() -> Word[]
}

可以看到该结构体并没有做太多的事情,仅仅实现了四个协议的接口,同时做了个UnsafePointer类型的转换操作。该类型在Swift使用得比较多,例如在NSData中,bytes属性的类型就是COpaquePointer,而该属性在Objective-C中的类型是const void*。

UnsafePointer

UnsafePointer一个泛型结构体,可以说是处理C指针最主要的结构体了。它包装并存储了类型为T的C指针,主要用于与C标准库交互。该类没有提供自动管理内存功能,所以我们在使用的时候需要注意内存的分配与释放。当在Swift能找到与T类型相对应的类型时,可以使用该类型,否则就考虑使用COpaquePointer。

在该类型的声明中,我们可以看到它定义了大量的方法来处理指针,具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
struct UnsafePointer<T> : BidirectionalIndex, Comparable, Hashable, LogicValue {
/// 构造一个空指针
init()
/// 将一个不透明指针转换为类型指定的C指针,注意这种转换是不安全的
init(_ other: COpaquePointer)
/// 从给定的内存地址中构造一个UnsafePointer。注意这种转换是不安全的
init(_ value: Int)
/// 从一个不同类型的指针中转换。注意这种转换是不安全的
init<U>(_ from: UnsafePointer<U>)
static func null() -> UnsafePointer<T>
static func alloc(num: Int) -> UnsafePointer<T>
func dealloc(num: Int)
/// 访问底层原始内存,获取并设置其值
var memory: T
/// 初始化指针指向的值,用于构造一个事先未存储对象的对象
func initialize(newvalue: T)
/// 获取指针指向的值,并将其从内存引用的位置移除。
/// 后置条件:值被销毁,且内存在再次使用前必须被初始化
func move() -> T
/// 将起始于source处的count个值赋值给未初始化的内存,将原始值转换到原始内存中,并且
/// 是从最后开始处理,一直到第一个。使用该方法将值拷贝到可能与原范围重叠的内存区域
/// 这要求source的位置先于self或者在self+count之后
func moveInitializeBackwardFrom(source: UnsafePointer<T>, count: Int)
/// Assign from count values beginning at source into initialized
/// memory, transforming the source values into raw memory.
/// 将起始于source处的count个值赋值给已初始化的内存,将原始值转换到原始内存中
func moveAssignFrom(source: UnsafePointer<T>, count: Int)
/// 拷贝起始于source的count个值到内存中,将源值转换到内存中
func moveInitializeFrom(source: UnsafePointer<T>, count: Int)
/// 拷贝起始于source的count个值到内存中
func initializeFrom(source: UnsafePointer<T>, count: Int)
/// 拷贝C元素到内存中
func initializeFrom<C : Collection where T == T>(source: C)
/// 销毁指针指向的对象
func destroy()
/// 销毁指针指向的count个对象
func destroy(count: Int)
func getLogicValue() -> Bool
subscript (i: Int) -> T
var hashValue: Int { get }
func succ() -> UnsafePointer<T>
func pred() -> UnsafePointer<T>
/// 以下是将其它类型的指针转换化UnsafePoint的构造器
init(_ cp: CConstPointer<T>)
init(_ cm: CMutablePointer<T>)
init(_ op: AutoreleasingUnsafePointer<T>)
init(_ cp: CConstVoidPointer)
init(_ cp: CMutableVoidPointer)
}
extension UnsafePointer<T> : Printable {
var description: String { get }
}

UnsafePointer提供了多个构造器,以从其它指针类型创建一个UnsafePointer。另外,CMutablePointer、CMutableVoidPointer、CConstPointer、CConstVoidPointer都提供了一个withUnsafePointer方法,该方法是让这些指针类型在一个闭包中可以像UnsafePointer类型一样使用。我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func StringFromCptr(inChars:CMutablePointer<CChar>) -> String {
var ptr:UnsafePointer<UInt8> = UnsafePointer<UInt8>(0)
inChars.withUnsafePointer({ p in ptr = UnsafePointer<UInt8>(p) })
var str = CString(ptr)
return NSString(CString:str)
}
var Len = 10
var Str = CChar[](count:Len, repeatedValue:CChar(0))
for var i = 0; i < Len; i++ {
Str[i] = CChar(i + 65)
}
var Result = StringFromCptr(&Str)
println("\(Result)")
// 输出:ABCDEFGHIJ

目前感觉在通常情况下,UnsafePointer使用得不多,平时接触到的更多的是CMutablePointer、CMutableVoidPointer、CConstPointer、CConstVoidPointer、AutoreleasingUnsafePointer这五种指针类型,这五种指针的基本使用方法我们在Swift中C指针的基本使用方法有简单介绍过,下面我们也讲讲它们的基本实现。

AutoreleasingUnsafePointer

该类型的基本定义是一个指向Objective-C指针的可变指针。这个类型有几种隐式转换来允许传递下面几种参数类型给一个一个C或ObjC API:

  1. nil, 作为null指针传入
  2. 被引用类型的in-out参数,作为一个带有自动释放所有权语义的可回写变量的指针传递
  3. UnsafePointer

与CMutablePointer不同的是,Swift不直接支持传递一个指向元素是Objc类指针数组的指针。与UnsafePointer不同的是,AutoreleasingUnsafePointer必须引用一个存储,该存储没有其引用值的引用计数。相反,UnsafePointer的操作假设引用的存储拥有其加载的值且可以对其进行修改。

该类型不像其它的C*Pointer类型一样携带一个所有者指针,因为它只需要引用in-out参数转换的结果,而这个参数已经有一个回写域的生命周期了。

我们来看看其声明:

1
2
3
4
5
6
7
struct AutoreleasingUnsafePointer<T> : Equatable, LogicValue {
func getLogicValue() -> Bool
/// Access the underlying raw memory, getting and
/// setting values.
var memory: T
}

CConstPointer与CConstVoidPointer

这两个类型都是C常量指针,分别对应于C中的const T和const void。它们没有自己的操作。它的值由owner-value对组成,也正因此,它不能直接传递给C函数。在桥接的过程中,它会维护一个owner的强引用,且指针值被传递到C或者Objective-C的入口点。这允许拥有堆存储的类型(如数组)将自己转换为一个指针,同时仍保证在调用的过程中仍然维护它们的存储。我们可以看下它们的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct CConstPointer<T> : Equatable {
let owner: AnyObject?
/// True if this is a scoped pointer, meaning it has a owner reference
/// that guarantees the lifetime of the referenced memory.
var scoped: Bool { get }
/// Make the pointer available as an UnsafePointer within a closure.
func withUnsafePointer<U>(f: UnsafePointer<T> -> U) -> U
}
struct CConstVoidPointer : Equatable {
let owner: AnyObject?
/// True if this is a scoped pointer, meaning it has a owner reference
/// that guarantees the lifetime of the referenced memory.
var scoped: Bool { get }
/// Make the pointer available as an UnsafePointer within a closure.
func withUnsafePointer<T, U>(f: UnsafePointer<T> -> U) -> U
}

CMutablePointer与CMutableVoidPointer

这两个类型都是C可变指针,分别对应于C中的T 和void ,其基本内容同上。

总结

由上面的分析,依据类型是否可以直接用于C函数声明,可以将这7种指针类型分为两类:

  1. 可直接用于C函数声明:COpaquePointer,UnsafePointer,AutoreleasingUnsafePointer,它们是对Builtin.RawPointer的封装,直接对应于C指针,其sizeof是单位字长
  2. 不可直接用于C函数声明:CMutablePointer, CConstPointer, CMutableVoidPointer, CConstVoidPointer。这几个类型都有一个owner属性用于管理实例的生命周期,可以直接从Swift对象的引用获得,另外还可以使用withUnsafePointer方法,该方法是让这些指针类型在一个闭包中可以像UnsafePointer类型一样使用。

另外有两点需要注意

  1. 非常量指针都实现了LogicValue协议,因此可以直接使用if a_pointer来判断其是否为NULL。
  2. nil类型实现了到所有指针类型的隐式类型转换,等价于C中的NULL,可以直接判断。

至于这些指针的使用,需要在根据实现情况来具体处理。

参考

  1. 《C语言接口与实现:创建可重用软件的技术》
  2. Swift.String to CMutablePointer
  3. Swift and C Interop Cont. (简析 Swift 和 C 的交互,Part 二)