'UnsafePointer<Int>'的初始化导致了悬空指针

6

我有一些代码可以创建H264参数集,如下:

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 我不认为你发布的问题解决了我的问题,我之前已经检查过了,但它并没有给出明确的答案。因此,下投票实际上是滥用机制的方式。 - Wingzero
真的吗?这些链接对我来说似乎很准确。传递不安全的指针一直是错误的做法。现在它是一个警告,将来会成为编译错误。这些链接也给出了正确的答案。 - matt
@matt 问题在于使用.withUnsafeBufferPointer {}会触发新的错误,请参见下面的答案和我的评论。我希望您可以重新打开此问题,因为这是一个稍微不同的问题,或者您发布的线程不能直接导致解决方案。 - Wingzero
@matt 另外,这是苹果的 API 要求我传递 UnsafePointer 类型。 - Wingzero
即使我可以从UnsafeBufferPointer中获取UnsafePointer,但这会破坏我需要返回值的代码。使用块将更复杂,以获得返回值?无论如何,问题是我的问题不能简单地通过查看上面的帖子来解决。我认为像我这样新手的视频解码+Swift的人会有同样的困惑。 - Wingzero
1个回答

9
解释这个警告最简单的方法是看一下引起它的一个例子,所以让我们从你对SPS的使用开始。
它是一个Array,因此它的后备缓冲区是由UInt8组成的,就像在C中一样。当你使用UnsafePointer(SPS)传递SPS时,它会为那个时刻创建一个有效的指向缓冲区的指针。问题在于,你随后可以通过将另一个值附加到它上来改变SPS。这意味着支持Array的缓冲区可能会移动到内存中的另一个位置。这也意味着现在是paramSet的一部分的指针已经无效了。
另一个问题是,如果你把这个指针传给某些东西,正如你在这里做的那样,其他函数可能会试图保留它,然后它就有了无效的指针。所以如果你希望其他函数保留指针,你需要手动管理内存,使用UnsafePointer和Unmanaged自己来完成。如果CMVideoFormatDescriptionCreateFromH264ParameterSets()不保留指针,则我分享的代码是正确的,如果它保留指针,则需要根据需要创建/销毁内存。
此外,值得注意的是,在这种情况下,你不能改变任何一个常量数组,但总的原则仍然是相同的。这意味着理论上它永远不会被改变,但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 > 0时,它不会为nil。(他们在Twitter或Swift论坛上声明过,我忘了哪个...)


有没有其他方法可以做到这一点,而不使用嵌套块?我真的不喜欢 'with... {}' 风格,你发送的代码很好,但似乎有点啰嗦? - Wingzero
除了让您的函数接受指针而不是转换为它们,实际上没有其他选择。在Swift中,正确使用指针通常非常冗长。如果您在其他地方做类似的事情,可以编写帮助程序函数,该函数获取N个数组并将这些N个指针提供给闭包。然后,您可以在此级别上使用2个withUnsafe块来完成此操作,而不是4个。 - bscothern
错误:无法将类型为“UnsafeBufferPointer<UnsafePointer<UInt8>>”的值转换为预期的参数类型“UnsafePointer<UnsafePointer<UInt8>>”。 - Wingzero
错误:无法将类型为“()”的返回表达式转换为返回类型“OSStatus”(又名“Int32”)。 - Wingzero
我建议你在你的端上尝试一下,并发布一个好的答案,否则我无法接受这个作为答案。 - Wingzero
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接