Sag*_*ual 5 unsafe-pointers swift
我一直在使用 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)
Run Code Online (Sandbox Code Playgroud)
虽然我没有找到关于不同种类背后理论的任何讨论,但我确实在本教程中找到了线索。有一个比较不同类型的图表,它说普通旧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变体。)
有关更多信息,我强烈推荐 Andrew Trick 在 WWDC 2020 上关于这个主题的演讲:Safely manage pointers in Swift。他详细介绍了表示 Swift 中指针生命周期的概念状态机,以及如何在指针类型之间进行转换和正确使用。(在谈到这个话题时,它尽可能接近马的嘴。)
另外,关于您的示例代码:@Sweeper 在评论中正确指出,如果您要分配Floats的缓冲区,则不应分配原始缓冲区并绑定其内存类型。通常,分配原始缓冲区不仅存在误认为所需缓冲区大小的风险,而且还存在不考虑填充(对于某些类型必须手动计算)的风险。
相反,您应该使用UnsafeMutableBufferPointer.allocate(capacity:)分配缓冲区,然后您可以写入该缓冲区。它正确地考虑了对齐和填充,所以你不会出错。
在 Swift 中,原始内存和类型化内存之间的区别非常微妙,Andy 在链接的演讲中对它的描述比我在这里要好得多,但是 tl;dr:原始内存是可以表示任何内容的无类型字节的集合,而类型化内存仅表示特定类型的值(并且不能安全地任意重新解释,除了少数例外,与 C 的重大背离!);你应该几乎从来都绑定内存手动,如果你绑定的内存来不平凡的类型,你几乎肯定是做错了。(不是说你在这里这样做,只是一个提示)
最后,关于Strideablevs.Collection和下标的主题——你可以下标到两者中的事实与 C 的行为相匹配,但在 Swift 中有一个微妙的语义区别。
下标UnsafePointer意味着它在 C 中所做的主要是什么:UnsafePointer知道它的基本类型,并引用内存中的单个位置,可以使用类型的对齐和填充来计算内存中该类型的下一个对象的位置(这就是它的Strideable一致性暗示); 下标允许您访问内存中相对于指针所指对象的几个连续对象之一。此外,就像在 C 中一样:因为你不知道一组这样的对象在哪里结束,你可以使用UnsafePointer没有边界检查的任意下标- 根本没有任何方法可以知道你是否正在尝试访问make 提前有效。
在另一方面,下标通过UnsafeBufferPointer就像访问一个元件内部在存储元件的集合。因为缓冲区开始和结束的位置有明确的界限,所以您可以进行界限检查,并且索引越界UnsafeBufferPointer更明显是一个错误。沿着这些思路,Strideable符合 onUnsafeBufferPointer没有多大意义:类型的“步幅”Strideable表明它知道如何到达“下一个”,但在整个UnsafeBufferPointer.
所以这两种类型最终都有一个有效执行相同操作的下标运算符,但在语义上具有非常不同的含义。
| 归档时间: |
|
| 查看次数: |
146 次 |
| 最近记录: |