我已经使用Apple的神经网络工具进行了很多工作,这意味着我已经使用不安全的指针进行了很多工作。我从C成长,而我与Swift在一起工作已经有一段时间了,所以我很习惯使用它们,但是关于它们的一件事让我感到非常沮丧。
我无法弄清楚为什么要从另一种派生一种不安全的指针需要付出任何努力。总的来说,这看起来应该是微不足道的,但是不同类型的初始值设定项具体说明了它们将采用哪种输入方式,而我很难确定规则。
一个简单而具体的例子,也许是让我最难过的一个例子
// The neural net components want mutable raw memory, and it's easier
// to build them up from the bottom, so: raw memory
let floats = 100
let bytes = floats * MemoryLayout<Float>.size
let raw = UnsafeMutableRawPointer.allocate(byteCount: bytes, alignment: MemoryLayout<Float>.alignment)
// Higher up in the app, I want to use memory that just looks like an array
// of Floats, to minimize the ugly unsafe stuff everywhere. So I'll use
// a buffer pointer, and that's where the first confusing thing shows up:
// This won't work
// let inputs = UnsafeMutableBufferPointer<Float>(start: raw, count: floats)
// The initializer won't take raw memory. But it will take a plain old
// UnsafePointer<Float>. Where to get that? you can get it from the raw pointer
let unsafeMutablePointer = raw.bindMemory(to: Float.self, capacity: floats)
// Buf if that's possible, then why wouldn't there be a buffer pointer initializer for it?
// Of course, with the plain old pointer, I can get my buffer pointer
let inputs = UnsafeMutableBufferPointer(start: unsafeMutablePointer, count: floats)
尽管我无法找到有关不同种类背后的理论的任何讨论,但确实在本教程中找到了一条线索。有一张图表比较了不同的类型,它说普通的旧UnsafePointer
字体是可以跨越的,但不是集合,而是收藏集,UnsafeBufferPointer
却不是不可跨越的。
我理解像集合这样的不可跨越的集合的概念。但是这两种类型的不安全指针都允许使用下标。它们就像常规数组一样工作,在我看来,它们都是可扩展的集合。也许那里有一个微妙的线索我想念。
为什么无法UnsafeBufferPointer
从可以从中获得的类型中获得UnsafePointer
?
这些类型之间的区别并不像您想象的那么大。从概念上讲,UnsafeBufferPointer
等效于的元组(UnsafePointer, Int)
,即它是指向内存中具有已知计数的元素的缓冲区的指针。UnsafePointer
相反,是指向内存中具有未知计数的元素的指针。UnsafePointer
更紧密地表示您可能习惯于C中的任意指针:它可能指向一个或多个元素,但仅凭其本身,就无法找出答案。
UnsafeBufferPointer
拥有已知计数也意味着它能够符合Collection
(这需要一个已知的开始和结束位置),而与UnsafePointer
没有该信息的相对。
Swift非常是一种语义语言,非常强调在类型系统中表达有关您可用工具的知识。如您所指出的,有些操作可以对一种类型执行,而不能对另一种类型执行-这是设计使某些操作难以正确执行。
如果您有正确的信息在它们之间移动,这些指针也可以转换:
UnsafeBufferPointer
有一个baseAddress
这是一个UnsafePointer
:给定一个缓冲区,可以随时“扔掉”有关计数,以获得潜在的无数指针UnsafePointer
和a count
,您还可以使用以下命令表示内存中存在缓冲区UnsafeBufferPointer.init(start:count:)
普遍的答案是:使用最具体的指针类型可以代表您拥有的数据。通常,Buffer
如果要指向多个元素并且知道有多少个元素,则最好使用指针的变体。同样,如果您要指向内存中的任意字节(可能具有也可能没有类型),则应Raw
尽可能使用指针。(当然,如果您需要写入内存中的这些位置,则也需要使用这些Mutable
变量的变体。)
有关更多信息,我强烈推荐WWDC 2020上Andrew Trick的主题演讲:安全地管理Swift中的指针。他详细介绍了代表Swift中指针寿命的概念状态机,以及如何在指针之间进行转换和正确使用指针类型。(涉及到主题时,它尽可能接近马的嘴!)
另外,关于示例代码:@Sweeper在注释中正确指出,如果要分配Float
s的缓冲区,则不应分配原始缓冲区,然后绑定其内存类型。您不仅冒着浪费所需缓冲区大小的风险,而且还冒着不考虑填充(对于某些类型必须手动计算)的风险。
相反,您应该使用UnsafeMutableBufferPointer.allocate(capacity:)
分配缓冲区,然后可以将其写入。它正确地考虑了对齐和填充,因此您不会出错。
在Swift中,原始内存和类型化内存之间的区别非常细微,Andy在链接的谈话中比我在这里描述的要好得多,但是tl; dr:您几乎永远不必手动绑定内存,并且如果将内存绑定到非平凡的类型,几乎可以肯定您做错了。(并不是说您在这里这样做,只是一个提示)
最后,以Strideable
vs.Collection
和下标为主题-您可以同时下标的事实与C的行为相匹配,但是在Swift中具有微妙的语义区别。
下标到某种程度上UnsafePointer
意味着它在C语言中的作用:一个人UnsafePointer
知道其基本类型,并使用填充和对齐方式,可以计算该类型下一个对象在内存中的位置(这就是其Strideable
一致性所隐含的含义);您可以像在C语言中那样使用下标运算符来做到这一点。此外,就像在C语言中一样:由于您不知道缓冲区的结束位置,因此可以使用UnsafePointer
无边界检查任意地对下标进行操作-根本就不会。不能以任何方式提前知道您要进行的访问是否有效。
另一方面,下标直通UnsafeBufferPointer
就像访问内存中元素集合内部的元素。由于缓冲区的开始和结束位置有明确的界限,因此您需要进行界限检查,而对的界限进行索引UnsafeBufferPointer
显然更是一个错误。按照这些思路,Strideable
一致性UnsafeBufferPointer
就没有多大意义:类型的“跨度”Strideable
表示它知道如何到达“下一个”,但是在整型之后并没有逻辑的“下一个”缓冲区UnsafeBufferPointer
。
因此,这两种类型都以有效执行相同操作的下标运算符结尾,但在语义上具有非常不同的含义。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句