警告:'UnsafeBufferPointer<T>' 的初始化会导致悬空的缓冲指针。

38

升级到Swift 5.2 / Xcode 11.4后,以下代码会出现警告:

extension Data {

    init<T>(from value: T) {
        var value = value
        let pointer = UnsafeBufferPointer(start: &value, count: 1)
        self.init(buffer: pointer)
    }

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.load(as: T.self) }
    }
}

在代码行let pointer = UnsafeBufferPointer(start: &value, count: 1)中,我得到了以下错误:

'UnsafeBufferPointer'的初始化导致悬垂指针

我可以使用@silenceWarning来解决问题,但这不是一个很好的解决方案。也许我需要将指针存储在某个地方,并在未来对其进行清理?


1
请查看以下链接:https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_release_notes 并搜索“danling”。似乎 https://bugs.swift.org/browse/SR-2790 更全面讨论了此问题。 - Roy Falk
函数崩溃(位于$0.load(as: T.self)行),线程1:EXC_BAD_ACCESS(代码=1,地址=0x20),XCODE 11.5 Swift5。 - Anees
6个回答

23

我也遇到了这些烦人的警告。

var str = "aaaaabbbbbccccc"
var num1 = 1
var num2 = 22

var data = Data()
// Initialization of 'UnsafeBufferPointer<String>' results in a dangling buffer pointer
data.append(UnsafeBufferPointer(start: &str, count: 1)) 
// Initialization of 'UnsafeBufferPointer<Int>' results in a dangling buffer pointer
data.append(UnsafeBufferPointer(start: &num1, count: 1))
// Initialization of 'UnsafeBufferPointer<Int>' results in a dangling buffer pointer 
data.append(UnsafeBufferPointer(start: &num2, count: 1)) 

考虑到@greg的答案,我把Data.append放进了withUnsafePointer的闭包里,这样就不会再出现警告了。
withUnsafePointer(to: &str) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } // ok
withUnsafePointer(to: &num1) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } // ok
withUnsafePointer(to: &num2) { data.append(UnsafeBufferPointer(start: $0, count: 1)) } // ok

这里是扩展程序

extension Data {
    init<T>(value: T) {
        self = withUnsafePointer(to: value) { (ptr: UnsafePointer<T>) -> Data in
            return Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
        }
    }

    mutating func append<T>(value: T) {
        withUnsafePointer(to: value) { (ptr: UnsafePointer<T>) in
            append(UnsafeBufferPointer(start: ptr, count: 1))
        }
    }
}

DRY append(.init(value: value)) - Leo Dabus

13

我曾经写过一段代码,和你的几乎一模一样,也遇到了同样的警告。但我的代码有一点不同,这对本次讨论很重要。

init<T>(from value: T) {
    var value = value
    self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}

这仍会产生警告,指出UnsafeBufferPointer正在生成一个悬空指针,但提示信息说“仅在对‘init(start:count :)’的调用期间产生有效指针”

但是从UnsafeBufferPointer返回的内容没有分配给任何东西,所以即使我试图在init的作用域之外使用它,我也无法使用它。因此,编译器在这里警告我不要做我无法做的事情。

我猜Data.init(buffer:)可能会存储ptr,但我认为如果它接受UnsafeBufferPointer,则承担使用它的责任。

不管怎样,这仍然不能真正解决你的问题。我用下面的方法解决了这个警告:

init<T>(from value: T) {
    var value = value
    var myData = Data()
    withUnsafePointer(to:&value, { (ptr: UnsafePointer<T>) -> Void in
        myData = Data( buffer: UnsafeBufferPointer(start: ptr, count: 1))
    })
    self.init(myData)
}

这样做不会产生警告,并且似乎可以工作(至少在我的应用程序中)。它是否能通过专家们的审核是另一回事。
有点让我怀念HLock和HUnlock的日子。

5

这个从来就不安全,很高兴Swift团队已经清理了它:

let pointer = UnsafeBufferPointer(start: &value, count: 1)

在此代码行的结尾,pointer 立即失效。没有保证 value 即使存在于下一行代码中。我不确定你想要达到什么目的,但这从来都不是一种安全的方式。你可能正在寻找的是其中一种 .withUnsafeBytes 方法,具体取决于你正在处理的内容。

7
尽管你的回答可能是正确的,但最好能举个例子说明可能出现的失败情况。网上有一些使用Unsafe*Pointer进行强制转换和转换的例子(https://dev59.com/VV4c5IYBdhLWcg3w3dZ_#27456220),会产生这个警告。 - Roy Falk
你可以通过在指针初始化和self.init调用之间使用锁对象添加一些“延迟”来测试,这些锁对象在另一个线程中使用。设置断点,使锁在另一个线程中被占用,这将延迟此代码的执行。顺便说一句,它也可能无法捕获我没有测试过的情况。 - Alexander Volkov
1
它怎么会“立即无效”?只要您控制值的生命周期,它看起来完全有效。这在低级编码中非常常见。 - Sergei

4

这是一个正确的解决方案:


extension Data {

    init<T>(from value: T) {
        // 1
        let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
        // 2
        pointer.initialize(to: value)
        defer {
            pointer.deinitialize(count: 1)
            pointer.deallocate()
        }
        // 3
        let bufferPointer = UnsafeBufferPointer(start: pointer, count: 1)
        self.init(buffer: bufferPointer)
    }

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.load(as: T.self) }
    }
}
  1. 您可以使用UnsafeMutablePointer.allocate来分配内存。泛型参数让Swift知道您正在使用指针来加载和存储值。
  2. 在使用之前必须初始化有类型的内存,并在使用后进行去初始化。您可以使用initialize和deinitialize方法来执行这些操作。
  3. 将指针转换为UnsafeBufferPointer。它将指向安全内存直到初始化程序结束。

1

在这里找到了一个不错的答案将Swift数字类型往返转换为Data

// value into Data
let data = withUnsafeBytes(of: value) { Data($0) }
// Data into value
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )

1

根据Stack Overflow上的这个回答(https://dev59.com/zFoT5IYBdhLWcg3w6i23#38024025),我是这样做的。

do {
  let array: [Float] = [1.2, 2.2]
  let data = withUnsafeBytes(of: array) { Data($0) }
  
  let restore: [Float] = data.withUnsafeBytes {
    $0.load(as: [Float].self)
  }
  
  array == restore //true
}

你可以使用你自己的类型,而不是 [Float]


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