【问题标题】:Swift Mutating Data at Range范围内的 Swift 变异数据
【发布时间】:2018-09-25 16:38:58
【问题描述】:

我正在构建一个看起来像这样的Data 对象:

struct StructuredData {
  var crc: UInt16
  var someData: UInt32
  var someMoreData: UInt64
  // etc.
}

我正在运行一个 CRC 算法,该算法将从字节 2 开始,进程长度为 12。

当CRC返回时,它必须存在于Data对象的开头。在我看来,我的选择是:

  1. 生成一个不包含 CRC 的 Data 对象,对其进行处理,然后构建另一个包含 CRC 的 Data 对象(这样我现在拥有的 CRC 值将位于 @ 的开头987654326@对象。

  2. 生成数据对象以包含一个清零的CRC,然后在[0..<2] 范围内改变数据。

显然,2 会更好,因为它使用更少的内存和更少的处理,但我不确定这种类型的优化是否有必要了。我还是宁愿选择 2,除非我不知道如何在给定的索引范围内改变数据。非常感谢任何帮助。

【问题讨论】:

    标签: swift nsdata unsafe-pointers


    【解决方案1】:

    我不推荐使用这种方式来改变Data

    data.replaceSubrange(0..<2, with: UnsafeBufferPointer(start: &self.crc, count: 1))
    

    请试试这个:

    data.replaceSubrange(0..<2, with: &self.crc, count: 2)
    

    很难解释为什么,但我会尝试...

    在 Swift 中,inout 参数以copy-in-copy-out 语义工作。当你写这样的东西时:

    aMethod(&param)
    
    • Swift 分配了一些足够大的区域来保存param 的内容,

    • param复制到区域中,(copy-in)

    • 通过传递区域地址调用方法,

    • 当从调用返回时,将该区域的内容复制回param(复制出)。

    在许多情况下,Swift 通过传递param 的实际地址来优化(即使在-Onone 设置中也可能发生)这些步骤,但没有明确记录。

    所以,当inout 参数传递给UnsafeBufferPointer 的初始化器时,UnsafeBufferPointer 接收到的地址可能指向一个临时区域,该临时区域将在初始化器完成后立即释放。

    因此,replaceSubrange(_:with:) 可能会将已释放区域中的字节复制到Data

    我相信第一个代码在这种情况下会起作用,因为crc 是结构的属性,但是如果有一个简单且安全的替代方案,您最好避免使用不安全的方法。


    Brandon Mantzey 自己的回答的评论。

    data.append(UnsafeBufferPointer(start: &self.crcOfRecordData, count: 1))
    

    使用上述含义中的安全。由于上述相同的原因,这不安全

    我会这样写:

    data.append(Data(bytes: &self.crcOfRecordData, count: MemoryLayout<UInt16>.size))
    

    (假设crcOfRecordData的类型为UInt16。)

    如果您不喜欢创建额外的Data 实例,您可以将其写为:

    withUnsafeBytes(of: &self.crcOfRecordData) {urbp in
         data.append(urbp.baseAddress!.assumingMemoryBound(to: UInt8.self), count: MemoryLayout<UInt16>.size)
    }
    

    这个在注释中没有提到,但是按照上面safe的意思,下面这行不是safe

    let uint32Data = Data(buffer: UnsafeBufferPointer(start: &self.someData, count: 1))
    

    都是一样的原因。

    我会这样写:

    let uint32Data = Data(bytes: &self.someData, count: MemoryLayout<UInt32>.size)
    

    不过,可观察到的意外行为可能会在某些非常有限的条件下发生,而且概率很小。

    只有满足以下两个条件时才会发生这种行为:

    1. Swift 编译器生成非优化的copy-in-copy-out 代码

    2. 在很窄的时间段之间,由于时间区域被释放,直到append方法(或Data.init)完成整个内容的复制,该区域被修改以备其他用途。

      李>

    条件 #1 仅在当前 Swift 实现中的有限情况下才会成立。

    条件 #2 仅在多线程环境中很少发生。 (不过,Apple 的框架使用了许多隐藏线程,您可以在 Xcode 的调试器中找到。)

    事实上,我没有看到任何关于上述不安全案例的问题,我的安全可能有点矫枉过正

    但是替代的安全代码并没有那么复杂,是吗? 在我看来,你最好习惯于使用all-cases-safe代码。

    【讨论】:

      【解决方案2】:

      我想通了。实际上,我遇到了一个令我难以置信的语法错误,因为我以前从未见过。

      答案如下:

      data.replaceSubrange(0..<2, with: UnsafeBufferPointer(start: &self.crc, count: 1))
      

      【讨论】:

      • 很遗憾,您的代码不安全...请检查我的回答。
      • 谢谢。我不想离题太远,但开始一个新问题似乎有点过分。根据您在回答中所说的,您会说data.append(UnsafeBufferPointer(start: &amp;self.crcOfRecordData, count: 1)) 是安全的吗?
      • 如果不是,您会推荐什么替代方案?如果您认为我应该开始一个新问题,请告诉我。再次感谢。
      • 等一下,我会更新我的答案,至于现在,我没有足够的时间。
      • 完成。请检查我答案的添加部分。
      猜你喜欢
      • 2017-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-05
      • 2021-08-27
      • 1970-01-01
      • 2012-10-04
      • 2016-10-26
      相关资源
      最近更新 更多