`UnsafeMutablePointer.initialize()` 到底做了什么?

What does `UnsafeMutablePointer.initialize()`actually do?

以下是我的猜测。请大家指出我理解错误的部分。

如果我有一个class,其中一个实例占用128位,叫做Class128Bits。我的程序在 64 位计算机上运行。

首先,我打电话给let pointer = UnsafeMutablePointer<Calss128Bits>.allocate(capacity: 2) 内存布局应如下所示:

000-063 064 bits chaos
064-127 064 bits chaos
128-255 128 bits chaos
256-383 128 bits chaos

如果我调用pointer.pointee = aClass128Bits,它会崩溃,因为前两个网格中的指针还没有被初始化。访问它们指向的内容会导致不可预测的结果。

但是如果我调用 pointer.initialize(to: aClass128Bits, count: 2),指针可以这样初始化:

000-063 address to offset 128
064-127 address to offset 256
128-255 a copy of aClass128Bits
256-383 a copy of aClass128Bits

那么任何访问都是安全的。 然而,这无法解释为什么 UnsafeMutablePointer<Int> 不会崩溃。


原创

我面临的情况:

指向 Int 的指针工作正常,但指向 String 的指针崩溃了。 我知道我需要像这样初始化它:

但是我看不出为什么要通过"42"两次。 在 C 中,我可能会做类似这样的事情:

char *pointer = (char *)malloc(3 * sizeof(char));
memcpy(pointer, "42", 3);
free(pointer)

如果allocate等于mallocfree等于deallocatememcpy等于pointee{ set }, 那么 initializedeinitialize 实际上做了什么? 为什么我的代码会崩溃?

从文档中可以得出结论,.initialize() 是一种方法:

Initializes memory starting at self with the elements of source.

.deinitialize()是一种方法:

De-initializes the count Pointees starting at self, returning their memory to an uninitialized state.

我们应该明白,当我们使用UnsafeMutablePointer时,我们应该自己管理内存。上面描述的方法可以帮助我们做到这一点。

所以在你的情况下让我们分析你提供的例子:

let pointer = UnsafeMutablePointer<String>.allocate(capacity: 1)
// allocate a memory space

pointer.initialize(to: "42")
// initialise memory

pointer.pointee // "42"
// reveals what is in the pointee location

pointer.pointee = "43"
// change the contents of the memory

pointer.deinitialize()
// return pointer to an unintialized state

pointer.deallocate(1)
// deallocate memory

所以你的代码崩溃是因为你没有初始化内存并尝试设置值。

以前在 objective-c 中,当我们处理对象时,我们总是使用 [[MyClass alloc] init]]

在这种情况下:

分配:

allocates a part of memory to hold the object, and returns the pointer.

初始化:

sets up the initial parameters of the object and returns it.

所以基本上.initialize() 将值设置为分配的内存部分。当您仅使用 alloc 创建对象时,您只设置对堆中空内存部分的引用。当您调用 .initialize() 时,您为堆中的此内存分配设置了值。

很好 article 关于指针。

你需要 initialize() 的一个原因可能是

ARC.

在了解 ARC 的工作原理时,您最好考虑局部范围变量:

func test() {
    var refVar: RefType = initValue  //<-(1)
    //...
    refVar = newValue //<-(2)
    //...
    //<-(3) just before exiting the loacl scope
}

对于如 (2) 的常见赋值,Swift 生成如下代码:

swift_retain(_newValue)
swift_release(_refVar)
_refVar = _newValue

(假设 _refVar_newValue 是非托管伪变量。)

Retain表示引用计数加1,release表示引用计数减1。


但是,想一想当在 (1) 处分配初始值时会发生什么。

如果生成了通常的赋值代码,代码可能会在这一行崩溃:

swift_release(_refVar)

因为为 var 新分配的区域可能充满垃圾,所以 swift_release(_refVar) 无法安全执行。

用零(空)填充新区域并 release 安全地忽略空可能是一种解决方案,但它有点多余且无效。

所以,Swift生成这样的初始值赋值代码:

(对于已经保留的值,如果您知道所有权模型,则为您所有。)

_refVar = _initValue

(对于未保留的值,意味着您还没有所有权。)

swift_retain(_initValue)
_refVar = _initValue

这是initialize

不释放垃圾数据,并赋初值,需要时保留。

(上面对"usual assignment"的解释稍微简化了一些,Swift不需要的时候省略了swift_retain(_newValue)。)


在 (3) 处退出本地作用域时,Swift 只会生成此类代码:

swift_release(_refVar)

所以,这是 deinitialize


当然,您知道像 Int 这样的原始类型不需要保留和释放,因此 initializedeinitialize 对于此类类型可能是 donothing

并且当您定义一个包含一些引用类型属性的值类型时,Swift 生成专门用于该类型的 initializedeinitialize 过程。


局部范围示例适用于分配在堆栈上的区域,initialize()deinitialize() of UnsafeMutablePointer 适用于分配在堆中的区域。

而 Swift 正在发展 swift,您可能会在将来找到需要 initialize()deinitialize() 的另一个原因,您最好将其作为习惯 initialize()deinitialize() 所有分配的 UnsafeMutablePointer 任何 Pointee 类型。

let pointer0 = UnsafeMutablePointer<String>.allocate(capacity: 1)
let pointer1 = UnsafeMutablePointer<Int>.allocate(capacity: 1)

让我们检查两者的大小

MemoryLayout.size(ofValue: pointer0) // 8
MemoryLayout.size(ofValue: pointer1) // 8

让我们检查 .pointee 的值

pointer0.pointee // CRASH!!!

pointer1.pointee // some random value

为什么?答案很简单。我们分配了 8 个字节,独立于 "associated" 类型。现在很清楚了,内存中的 8 个字节不足以存储任何字符串。必须间接引用底层内存。但是那里有一些 8 个随机字节......加载内存中的地址由 8 个随机字节表示的字符串很可能会崩溃:-)

为什么第二种情况没有崩溃? Int 值是 8 个字节长,地址可以表示为 Int 值。

让我们在 Playground 中尝试一下

import Foundation

let pointer = UnsafeMutablePointer<CFString>.allocate(capacity: 1)    

let us = Unmanaged<CFString>.passRetained("hello" as CFString)

pointer.initialize(to: us.takeRetainedValue())
print(pointer.pointee)

us.release()

// if this playground crash, try to run it again and again ... -)
print(pointer.pointee)

看看它给我打印了什么:-)

hello
(
    "<__NSCFOutputStream: 0x7fb0bdebd120>"
)

后面没有奇迹。 pointer.pointee 试图表示内存中的内容,哪个地址存储在我们的指针中,作为其关联类型的值。它永远不会为 Int 崩溃,因为内存中某处的每 8 个连续字节都可以表示为 Int。

Swift 使用 ARC,但创建 Unsafe[Mutable]Pointer 不会为 T 的实例分配任何内存,销毁它也不会为其释放任何内存。

类型化内存必须在使用前初始化并在使用后取消初始化。这是分别使用 initialize 和 deinitialize 方法完成的。只有非平凡类型才需要取消初始化。也就是说,包括去初始化是一种让代码面向未来的好方法,以防您更改为非平凡的东西

为什么赋值给具有 Int 值的 .pointee 不会崩溃?

  1. 初始化存储值的地址
  2. 赋值给指针更新存储地址的值

不初始化很可能会崩溃,只是在某个随机地址只修改内存中的8个字节的可能性较小。

试试这个

import Darwin
var k = Int16.max.toIntMax()
typealias MyTupple = (Int32,Int32,Int8, Int16, Int16)

var arr: [MyTupple] = []
repeat {
    let p = UnsafeMutablePointer<MyTupple>.allocate(capacity: 1)
    if k == 1 {
        print(MemoryLayout.size(ofValue: p), MemoryLayout.alignment(ofValue: p),MemoryLayout.stride(ofValue: p))
    }
    arr.append(p.pointee)
    k -= 1
    defer {
        p.deallocate(capacity: 1)
    }
} while k > 0

let s = arr.reduce([:]) { (r, v) -> [String:Int] in
    var r = r
    let c = r["\(v.0),\(v.1),\(v.2),\(v.3)"] ?? 0
    r["\(v.0),\(v.1),\(v.2),\(v.3)"] = c + 1
    return r
}
print(s)

我收到了

8 8 8
["0,0,-95,4104": 6472, "0,0,0,0": 26295]
Program ended with exit code: 0

看起来不是很随意,是吗?这就解释了为什么指向 Int 的类型化指针发生崩溃的可能性很小。