`UnsafeMutablePointer.initialize()`实际上做了什么?

时间:2016-09-27 05:55:51

标签: ios c swift pointers automatic-ref-counting

以下是基于我的猜测。有人请指出我理解不正确的部分。

如果我有一个类,其实例占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>不会崩溃。

原始

我面临的情况: Crash

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

但我看不出我需要两次传递"42"的原因。 在C中,我可能会做类似这样的事情:

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

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

3 个答案:

答案 0 :(得分:2)

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个字节,独立于&#34;关联的&#34;类型。现在很清楚,内存中的8个字节不足以存储任何String。必须间接引用底层内存。但是那里有8个随机字节......用8个随机字节表示的地址加载内存中的内容很可能会崩溃: - )

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

让我们在游乐场尝试

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] Poiner并不为T的实例分配任何内存,破坏它不会为它释放任何内存。

键入的内存必须在使用前初始化,并在使用后取消初始化。这分别使用initialize和deinitialize方法完成。只有非平凡类型才需要取消初始化。也就是说,包括取消初始化是一种很好的方法,可以在将您的代码更改为非平凡的情况时对其进行验证。

为什么没有使用Int值崩溃分配给.pointee?

  1. 初始化存储值的地址
  2. 指派人员更新存储地址的值
  3. 没有初始化它很可能会崩溃,只是通过在某个随机地址中仅修改内存中的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类型指针的崩溃是不太可能的。

答案 1 :(得分:1)

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

  

使用源元素从self开始初始化内存。

.deinitialize()方法是:

  

从自我开始取消初始化计数指针,返回它们   记忆到未初始化的状态。

我们应该明白,当我们使用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

因此,您的代码崩溃是因为您没有初始化内存并尝试设置值。

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

在这种情况下:

分配:

  

分配一部分内存来保存对象,并返回   指针。

INIT:

  

设置对象的初始参数并将其返回。

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

关于指针的好article

答案 2 :(得分:1)

您需要func test() { var refVar: RefType = initValue //<-(1) //... refVar = newValue //<-(2) //... //<-(3) just before exiting the loacl scope } 的一个原因,也就是现在唯一可能的原因是

ARC的

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

swift_retain(_newValue)
swift_release(_refVar)
_refVar = _newValue

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

_refVar

(假设_newValueRetain是非托管伪变种。)

release表示将引用计数递增1,swift_release(_refVar) 表示将引用计数递减1。

但是,请考虑当初始值赋值为(1)时会发生什么。

如果生成了通常的分配代码,代码可能会在此行崩溃:

swift_release(_refVar)

因为var的新分配区域可能会填充垃圾,因此release无法安全执行。

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

因此,Swift为初始值赋值生成了这种代码:

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

swift_retain(_initValue)
_refVar = _initValue

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

initialize

这是 swift_retain(_newValue)

不释放垃圾数据,并分配初始值,并在需要时保留它。

(上面对“通常作业”的解释略微简化,Swift在不需要时省略swift_release(_refVar) 。)

当退出(3)的本地范围时,Swift只生成这种代码:

deinitialize

所以,这是 Int

当然,您知道initialize等原始类型不需要保留和释放,因此deinitializedonothing可能是initialize这类型。

当您定义包含某些引用类型属性的值类型时,Swift会生成专用于该类型的deinitializeinitialize()过程。

本地范围示例适用于在堆栈上分配的区域,deinitialize()的{​​{1}}和UnsafeMutablePointer适用于堆中分配的区域。

Swift正在迅速发展,你可能会发现将来需要initialize()deinitialize()的另一个原因,你最好养成initialize()和{{{ 1}}所有deinitialize()类型的UnsafeMutablePointer已分配。