以下是基于我的猜测。有人请指出我理解不正确的部分。
如果我有一个类,其实例占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
等于malloc
,free
等于deallocate
,memcpy
等于pointee{ set }
,
然后initialize
和deinitialize
实际上做了什么?
为什么我的代码会崩溃?
答案 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?
没有初始化它很可能会崩溃,只是通过在某个随机地址中仅修改内存中的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的工作原理时,您最好考虑使用局部范围变量:
swift_retain(_newValue)
swift_release(_refVar)
_refVar = _newValue
对于通常的赋值为(2),Swift会生成如下代码:
_refVar
(假设_newValue
和Retain
是非托管伪变种。)
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
等原始类型不需要保留和释放,因此deinitialize
和donothing
可能是initialize
这类型。
当您定义包含某些引用类型属性的值类型时,Swift会生成专用于该类型的deinitialize
和initialize()
过程。
本地范围示例适用于在堆栈上分配的区域,deinitialize()
的{{1}}和UnsafeMutablePointer
适用于堆中分配的区域。
Swift正在迅速发展,你可能会发现将来需要initialize()
和deinitialize()
的另一个原因,你最好养成initialize()
和{{{ 1}}所有deinitialize()
类型的UnsafeMutablePointer
已分配。