【问题标题】:Can I use memory after pointer deinitialization?我可以在指针取消初始化后使用内存吗?
【发布时间】:2021-10-27 09:19:10
【问题描述】:

指针去初始化后可以使用内存吗?

例如

let q = UnsafeMutablePointer<Float>.allocate(capacity: 1)
q.pointee = 123.125
let bq = UnsafeRawBufferPointer(start: q.deinitialize(count: 1), count: 4) // (*)
for i in bq.reversed() {
    print(String(i, radix: 16), terminator: " ")
}

q.deallocate()
print()

这是我这个任务的核心解决方案:https://pl.spoj.com/problems/PP0504D/

任务中的问题很简单:编写一个函数,显示浮点值的机器字节表示。

float2bytes(123.125) prints 42 f6 40 0 但这不是问题的关键。

我对指针并不深入。我的疑问是:我可以在取消初始化后使用由另一个指针指向的内存(该内存未被释放)吗?取消初始化的真正含义是什么?

let p = UnsafeMutablePointer<Int>.allocate(capacity: 1)
p.initialize(to: 7)
print(p.pointee)
p.initialize(to: 9)
p.deinitialize(count: 1)
// something may crash the memory in this place?
print(p.pointee)
p.deallocate()

谁能告诉我deinicialize(count:)之后导致崩溃的代码?

【问题讨论】:

  • 您需要用英语解释您要解决的问题,因为 SO 的语言是英语,而链接的问题是波兰语。
  • 我更正了我的帖子

标签: swift pointers memory


【解决方案1】:

虽然@RobNapier 的回答肯定是正确的,但您也可以使用FloatbitPattern 属性访问其“原始位”:

let f: Float = 123.125
let bits = f.bitPattern

然后您可以将这些位分割成字节:

let bytesLittleEndian = stride(from: 0, to: UInt32.bitWidth, by: 8)
    .map { UInt8(truncatingIfNeeded: bits >> $0) }

let bytesBigEndian = stride(from: 0, to: UInt32.bitWidth, by: 8)
    .reversed()
    .map { UInt8(truncatingIfNeeded: bits >> $0) }

【讨论】:

  • 请注意,这不完全是“原始位”。 bitPattern 被定义为返回 IEEE 754 二进制交换格式,我预计它通常会是内存中的内容,但不保证完全相同。 (例如,可能会有不同的规范化,但如果这在实践中从未发生过,我不会感到惊讶。)withUnsafePointer(to:) 可能会返回不同的东西。 (但您绝对应该bitPattern 用于任何您想要“位”的东西。同意!这将比我的实际用途答案更好。)
【解决方案2】:

感谢 Rob Napier 的解释。当我还不知道指针时,我解决了这样的问题:

import Foundation

let k = Int(readLine()!)!

for _ in 1...k {
    let n = String(Float(readLine()!
    .trimmingCharacters(in: .whitespaces))!
    .bitPattern, radix: 16)
    let f = ((n + repeatElement("0", count: 8 - n.count))
        .reduce("") {
             "\($0)" + "\(($0.count + 1) % 3 == 0 ? "'" : "")" + "\($1)"
     } as String)
        .split(separator: "'")
    .map {
         $0 == "00" ? "0" : $0
     }
    .reduce("") {
         "\($0)\($1) "
    }
    print(f)
}

但现在我想知道如何为代码编写 UI 测试

//file
import Foundation

let k = Float(readLine()!)!
print(k)

【讨论】:

  • 你应该把这个发布到 codereview.stackexchange.com 这里有很多地方需要改进
【解决方案3】:

指针未被取消初始化。内存是。当您调用q.deinitialize(count: 1) 时,您已取消初始化q 指向的内存。然后读取未初始化的内存是无效的。它几乎肯定会“工作”,因为这是一个普通类型(浮点数),但它是无效的。

内存的状态多于“已分配”和“已解除分配”。它还需要绑定和初始化。当您调用deinitialize 时,内存会重新进入未初始化状态(但仍处于绑定状态),但在读取之前必须重新初始化。

有关详细信息,请参阅 WWDC 2020 的 Safely manage pointers in Swift

对于您描述的特定情况,您不需要内存管理。您可以直接向系统询问值的字节数:

let value: Float = 123.125

withUnsafeBytes(of: value) { q in
    for i in q.reversed() {
        print(String(i, radix: 16), terminator: " ")
    }
}

(我知道您可能还有其他问题正在解决。但这就是您将如何做您所描述的事情。)

对于您的第二个示例,您可能永远不会看到崩溃(因为 Float 是微不足道的)。但它的行为是不确定的。一旦您调用未定义的行为,您可能会对编译器允许应用的优化感到非常惊讶。例如,这可以不打印任何内容,或打印一些常量值。或者它可能会崩溃(可能不会,但它可能会)。或者它可以完美地工作,但仍然是错误的。

【讨论】:

  • 我总是wished 在文档中有一个简单的状态图,在使用指针 API 时可以参考。我总是发现自己回想起那个视频,但是搜索它以找到我需要的部分非常昂贵,而且我从来没有对我的解决方案建立良好的信心/理解:/
  • 非常令人沮丧的是,系统许多部分的唯一文档是 WWDC 视频,而且他们经常在几年后将其删除。
猜你喜欢
  • 2021-01-13
  • 2014-04-06
  • 2021-04-12
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
  • 2019-10-28
  • 1970-01-01
  • 2011-10-31
相关资源
最近更新 更多