以下代码可以在Swift Playground中运行:
import UIKit
func aaa(_ key: UnsafeRawPointer!, _ value: Any! = nil) {
print(key)
}
func bbb(_ key: UnsafeRawPointer!) {
print(key)
}
class A {
var key = "aaa"
}
let a = A()
aaa(&a.key)
bbb(&a.key)
这是打印在我的Mac上的结果:
0x00007fff5dce9248
0x00007fff5dce9220
为什么两次打印的结果不同?更有趣的是,当我更改 bbb 的函数签名使其与 aaa相同时 ,两次打印的结果相同。如果在这两个函数调用中使用
全局var 而不是 a.key ,则两次打印的结果是相同的。有谁知道为什么会发生这种奇怪的行为?
为什么两次打印的结果不同?
因为对于每个函数调用,Swift都会创建一个临时变量,该临时变量初始化为a.key
的getter
返回的值。每个函数都使用指向 其 给定临时变量的指针进行调用。因此,指针值可能会不同,因为它们引用了 不同的 变量。
之所以在这里使用临时变量,是因为A
它是非最终类,因此可以使其子类的getter和setter方法key
覆盖
子类(可以很好地将其实现为计算属性)。
因此,在未优化的构建中,编译器不能只是key
直接将的地址传递给函数,而必须依赖于调用getter(尽管在优化的构建中,此行为可以完全改变)。
您会注意到,如果将其标记key
为final
,您现在应该在两个函数中获得一致的指针值:
class A {
final var key = "aaa"
}
var a = A()
aaa(&a.key) // 0x0000000100a0abe0
bbb(&a.key) // 0x0000000100a0abe0
因为现在的地址key
可以
只被直接传递给函数,完全绕过其吸气剂。
但是,值得注意的是,通常来说,您 不应该 依赖此行为。在函数中获得的指针的值是纯实现细节,并且 不能
保证稳定。编译器可以随心所欲地调用函数,只是向您保证,所获得的指针将在调用期间有效,并且将指针初始化为期望值(如果可变,则对指针进行的任何更改)。呼叫者将看到被指示者)。
该规则的 唯一 例外是传递指向全局和静态存储变量的指针。Swift 确实
确保您获得的指针值对于该特定变量将是稳定且唯一的。从Swift团队关于与C指针交互的博客文章中(重点是我的):
但是,与其他Swift代码相比,与C指针进行交互本质上是不安全的,因此必须格外小心。特别是:
* 如果被调用方在返回后保存指针值以供使用,则不能安全地使用这些转换。这些转换产生的指针仅在呼叫期间有效。即使您将相同的变量,数组或字符串作为多个指针参数传递,也可能每次都收到不同的指针。
全局或静态存储的变量是一个例外。 您可以安全地将全局变量的地址用作持久唯一指针值,例如:作为KVO上下文参数。
因此,如果您将key
静态存储属性A
设为或只是将全局存储变量设为全局变量,则可以确保在两个函数调用中获得相同的指针值。
当我更改的功能
bbb
使其与相同时aaa
,两次打印的结果相同
这似乎是一项优化工作,因为我只能在-O建筑物和游乐场中进行复制。在未优化的版本中,添加或删除额外的参数无效。
(尽管值得注意的是,您不应在运动场中测试Swift行为,因为它们不是真正的Swift环境,并且可能会与使用编译的代码表现出不同的运行时行为swiftc
)
此行为的原因仅仅是一个巧合-第二个临时变量能够与第一个临时变量驻留在 相同的
地址(在第一个临时变量被释放之后)。当您在中添加额外的参数时aaa
,将在它们之间“分配”新变量以保存要传递的参数值,从而防止它们共享相同的地址。
在未优化的版本中,由于a
要调用getter以获取值的中间负载,因此无法观察到相同的地址a.key
。作为优化,如果编译器a.key
具有带有常量表达式的属性初始化程序,则它可以将其值内联到调用站点,从而消除了对此中间负载的需要。
因此,如果您提供a.key
一个不确定的值,例如var key = arc4random()
,那么您应该再次观察不同的指针值,因为a.key
不能再内联的值。
但是,无论原因如何,这都是一个 很好的 示例,说明如何 不 依赖变量(不是全局变量或静态存储的变量)的指针值-
因为您获得的值可以根据优化级别等因素而完全改变和参数计数。
inout
和 UnsafeMutable(Raw)Pointer
但是由于
withUnsafePointer(to:_:)
始终具有我想要的正确行为(实际上应该如此,否则此功能没有用),并且它还具有一个inout
参数。因此,我假设这些函数与inout
参数之间在实现上存在差异。
编译器对待inout
参数的方式与参数 略有
不同UnsafeRawPointer
。这是因为你可以变异的价值inout
在函数调用的参数,但你不能在发生变异pointee
的UnsafeRawPointer
。
为了使inout
参数值的任何变化对调用者可见,编译器通常具有两个选项:
将一个临时变量初始化为该变量的getter返回的值。使用指向该变量的指针来调用该函数,一旦函数返回,请使用临时变量的(可能是变异的)值来调用变量的setter。
如果它是可寻址的,则只需使用 直接 指向该变量的指针来调用该函数。
如上所述,编译器无法对未知的存储属性使用第二个选项final
(但这可以随着优化而改变)。但是,对于大值而言,始终依赖第一个选项可能会非常昂贵,因为必须将其复制。这对于具有写时复制行为的值类型
特别 有害,因为它们依赖于唯一性才能对其基础缓冲区执行直接突变-临时副本违反了这一点。
为了解决这个问题,Swift实现了一个特殊的访问器–
materializeForSet
。该访问器允许被调用方为调用方提供指向给定变量的 直接
指针(如果可寻址的话),否则将返回指向包含该变量副本的临时缓冲区的指针,此缓冲区需要在写完后写回给setter它已被使用。
前者是你与看到的行为inout
- 你得到一个 直接
指针来a.key
从后面materializeForSet
,所以你在这两个函数调用得到的指针值是相同的。
但是,materializeForSet
仅用于需要回写的功能参数,这说明了为什么不将其用于的原因UnsafeRawPointer
。如果将函数参数设置为aaa
并bbb
取UnsafeMutable(Raw)Pointer
s(
确实 需要回写),则应再次观察相同的指针值。
func aaa(_ key: UnsafeMutableRawPointer) {
print(key)
}
func bbb(_ key: UnsafeMutableRawPointer) {
print(key)
}
class A {
var key = "aaa"
}
var a = A()
// will use materializeForSet to get a direct pointer to a.key
aaa(&a.key) // 0x0000000100b00580
bbb(&a.key) // 0x0000000100b00580
但同样,就像上面说,这种行为是 不是 在为不属于全局或静态变量的依据。
我在这里创建了两个函数,它们几乎100%相同。唯一的区别是函数的regex字符集的顺序不同。为什么这两个函数会产生不同的输出?
问题内容: 我尝试检查Golang何时返回本地值为nil,然后使用此代码。 输出为0x0 但是当我只是使用println输出值时。 输出更改为0x0(0x93d40,0x0) 谁能解释这种行为的机制?谢谢! 问题答案: 首先,内建是非常不同的问题:在实现和目的上都不同。使用反射处理许多复杂的情况,而仅处理一些基本情况,并且仅用于“引导或调试”(如规范所述)。 在这种情况下,您将打印从返回的。在外观
我正在尝试编写一个Python实用程序,将Oracle的RAW字节字符串(作为字符串)转换为Guid,反之亦然。我试图重用我在C#中构建的同一个实用程序中的算法,但是从同一个字节数组构造一个和一个会产生不同的Guid/UUID。它们是一样的,不是吗?我读过UUID只是一个更好的术语。 在C#中,我有一个字节数组,< code>byte_array如下所示: 在 Python 中,我有一个字节数组,
<代码>car\U gear字段在数据库中填写为“stick”(斗杆)。在图像标记后,输出更改为“自动” 为什么结果是$car\u result1-
为什么运算符只应该是4个字节却生成12个字节?当我引用变量时,这只是引用数组第一个索引的内存地址。实际上,我打印了第一个索引的内存地址,并将其与进行了比较,它们产生了相同的内存地址结果,这证实了它们都引用了数组的第一个索引,但是“array”产生了12个字节,而产生了4个字节。
我有两个元素,我想让它们淡出,停留3秒,然后使用jQuery一个接一个淡出,但效果是一次应用的。 null null 我尝试使用和方法,但都不起作用。是什么导致了这个问题?有没有更好的方法来实现这个目标?