【问题标题】:Initialization of 'UnsafePointer<Int>' results in a dangling pointer [duplicate]'UnsafePointer<Int>' 的初始化导致指针悬空[重复]
【发布时间】:2020-07-30 10:56:35
【问题描述】:

所以我有一些代码来创建 H264ParameterSets,例如:

var formatDesc: CMVideoFormatDescription?

func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
    if formatDesc != nil { formatDesc = nil }

    let paramSet = [UnsafePointer<UInt8>(SPS), UnsafePointer<UInt8>(PPS)]
    let paramPointers = UnsafePointer<UnsafePointer<UInt8>>(paramSet)
    let paramSizes = UnsafePointer<Int>([SPS.count, PPS.count])

    let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramPointers, parameterSetSizes: paramSizes, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)

    return status
}

从 Xcode 11.4 开始,我收到了有关 UnsafePointer() 的警告,这在以前似乎没有发生过:

Initialization of UnsafePointer<UInt8> results in a dangling pointer

Initialization of UnsafePointer<UnsafePointer<UInt8>> results in a dangling pointer

Initialization of UnsafePointer<Int> results in a dangling pointer

我不确定我们为什么会看到这个?以及如何删除警告?提前致谢。

【问题讨论】:

  • @matt 我不认为您发布的问题解决了我的问题,我之前已经检查过,但没有给出明确的答案。所以downvote实际上是在滥用它的工作机制
  • 真的吗?这些链接在我看来是正确的。传递不安全的指针总是错误的。现在是警告,将来会出现编译错误。这些链接也给出了正确的答案。
  • @matt 问题是使用 .withUnsafeBufferPointer {} 会触发新的错误,请参阅下面的答案和我的评论。我希望您可以重新打开它,因为这是一个稍微不同的问题,或者您发布的主题不能直接导致解决方案。
  • @matt 此外,Apple 的 API 要求我传递 UnsafePointer 类型。
  • 即使我可以从 UnsafeBufferPointer 获取 UnsafePointer,它也会破坏我需要返回的代码。使用块会有返回值会更复杂吗?无论如何,关键是我的问题不能简单地通过查看上面的帖子来解决。我想像我这样不熟悉 Videodecode+Swift 的人会有同样的困惑。

标签: ios swift


【解决方案1】:

解释此警告的最简单方法是查看导致该警告的一种情况。那么让我们从你对SPS的使用开始吧。

它是一个Array&lt;UInt8&gt;,因此它由UInt8 的缓冲区支持,就像在C 中一样。当您将SPSUnsafePointer&lt;UInt8&gt;(SPS) 一起传递时,它会在该时刻创建一个指向缓冲区的有效指针。问题是你可以通过向它附加另一个值来改变SPS。这意味着支持Array 的缓冲区可能会移动到内存中的另一个位置。这意味着您现在属于 paramSet 的指针无效。

另一个问题是,如果你将这个指针传递给某个东西,就像你在这种情况下所做的那样,另一个函数可能会尝试保持它,然后它有一个无效的指针。因此,如果您希望其他函数保留指针,您需要自己手动使用UnsafePointers 和Unmanaged 管理内存。如果CMVideoFormatDescriptionCreateFromH264ParameterSets() 没有保留指针,那么我将分享的代码是正确的,如果是,您需要调整它以根据需要创建/销毁内存。

另外值得注意的是,在这种情况下,你不能改变你拥有的任何Arrays,因为它们是常量,但总的来说原理仍然相同。这意味着理论上它永远不会发生变异,但 Swift 编译器更愿意帮助我们编写尽可能安全和正确的代码,即使是 UnsafePointer 类型也是如此。

那么你怎么能解决这个问题呢?您需要能够调用withUnsafeBufferPointer,然后通过UnsafeBufferPointer 访问指针,如下所示:

var formatDesc: CMVideoFormatDescription?

func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
    if formatDesc != nil { formatDesc = nil }

    let status = SPS.withUnsafeBufferPointer { SPS in
        PPS.withUnsafeBufferPointer { PPS in
            let paramSet = [SPS.baseAddress!, PPS.baseAddress!]
            let paramSizes = [SPS.count, PPS.count]
            return paramSet.withUnsafeBufferPointer { paramSet in
                paramSizes.withUnsafeBufferPointer { paramSizes in
                    CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSet.baseAddress!, parameterSetSizes: paramSizes.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
                }
            }
        }
    }
    return status
}

这种方法有效的原因是,对于withUnsafeBufferPointer 的范围,排他性法则正在保护数组,因此它们不能被变异。

如果您担心baseAddress! 的用法,您可以检查它不是nil,但根据编译器工程师的说法,当count &gt; 0 时,它保证不是nil(他们已经在任一Twitter 或我忘记的 Swift 论坛...)。

【讨论】:

  • 是否有其他方法可以做到这一点,而不是使用嵌套块?真的不喜欢'with...{}'风格,你发送的代码很好,但似乎有点冗长?
  • 除了让您的函数采用指针而不是转换为指针之外,没有其他选择。 Swift 中的指针通常非常冗长,无法正确使用。如果你在其他地方做类似的事情,你可以编写辅助函数,它接受 N 个数组并提供这些 N 个指向闭包的指针。然后您可以在此级别的 2 个 withUnsafe 块中执行此操作,而不是 4 个。
  • 抱歉,您的代码无法编译。首先有一个错字:需要将 paramPointers 更改为 paramSet。但是在此之后,它会引发警告:Constant 'status' inferred to have type '()',这可能是意外的
  • 错误:无法将“UnsafeBufferPointer”类型的值转换为预期的参数类型“UnsafePointer
  • 错误:无法将“UnsafeBufferPointer>”类型的值转换为预期的参数类型“UnsafePointer>”