`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
等于malloc
,free
等于deallocate
,memcpy
等于pointee{ set }
,
那么 initialize
和 deinitialize
实际上做了什么?
为什么我的代码会崩溃?
从文档中可以得出结论,.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
这样的原始类型不需要保留和释放,因此 initialize
和 deinitialize
对于此类类型可能是 donothing
。
并且当您定义一个包含一些引用类型属性的值类型时,Swift 生成专门用于该类型的 initialize
和 deinitialize
过程。
局部范围示例适用于分配在堆栈上的区域,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 不会崩溃?
- 初始化存储值的地址
- 赋值给指针更新存储地址的值
不初始化很可能会崩溃,只是在某个随机地址只修改内存中的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 的类型化指针发生崩溃的可能性很小。
以下是我的猜测。请大家指出我理解错误的部分。
如果我有一个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
等于malloc
,free
等于deallocate
,memcpy
等于pointee{ set }
,
那么 initialize
和 deinitialize
实际上做了什么?
为什么我的代码会崩溃?
从文档中可以得出结论,.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
这样的原始类型不需要保留和释放,因此 initialize
和 deinitialize
对于此类类型可能是 donothing
。
并且当您定义一个包含一些引用类型属性的值类型时,Swift 生成专门用于该类型的 initialize
和 deinitialize
过程。
局部范围示例适用于分配在堆栈上的区域,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 不会崩溃?
- 初始化存储值的地址
- 赋值给指针更新存储地址的值
不初始化很可能会崩溃,只是在某个随机地址只修改内存中的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 的类型化指针发生崩溃的可能性很小。