3

假设我有一个外部函数:

-- | Turns char* of the given size into a char* of size 16.
doSomethingFfi :: Ptr CUChar -> Ptr CUChar -> CSize -> IO ()
doSomethingFfi = undefined

该函数是纯函数,因此我想将其表示为 Haskell 中的纯函数:

doSomething :: ByteArray bytes => bytes -> bytes
doSomething bs = unsafePerformIO $
  alloc 16 $ \outPtr ->
  withByteArray bs $ \inPtr ->
    doSomethingFfi outPtr inPtr (fromIntegral $ length bs)

(这里我使用allocfrom memory。)

unsafePerformIO我的理解是和之间的唯一区别unsafeDupablePerformIO是后者中的 IO 操作可以在不进行任何清理的情况下静默终止。

在我上面的例子中,基本上发生了两个 IO 操作: 1. 内存分配;2.外呼。我不关心2,因为它是纯粹的,但是我担心内存。

如果计算被静默中断,是否可以保证以这种方式分配的内存不会泄漏?如果外部功能还需要我必须分配/清理的临时存储并且我用于alloca此目的,那么它仍然可以安全使用unsafeDupablePerformIO吗?

4

1 回答 1

2

大多数情况下,正如我在评论中解释的那样,但不完全是:

alloca

按照alloca目前的实施,这是安全的。alloca是通过调用来实现的allocaBytesAligned,它是这样定义的:

allocaBytesAligned :: Int -> Int -> (Ptr a -> IO b) -> IO b
allocaBytesAligned (I# size) (I# align) action = IO $ \ s0 ->
     case newAlignedPinnedByteArray# size align s0 of { (# s1, mbarr# #) ->
     case unsafeFreezeByteArray# mbarr# s1 of { (# s2, barr#  #) ->
     let addr = Ptr (byteArrayContents# barr#) in
     case action addr     of { IO action' ->
     case action' s2      of { (# s3, r #) ->
     case touch# barr# s3 of { s4 ->
     (# s4, r #)
  }}}}}

这会在垃圾收集堆中分配固定内存。如果您的操作提前中止,那么垃圾收集器迟早会回收它分配的内存。

alloc

不一定是安全的,但实际上在实践中可能是安全的。alloc是使用类方法定义的allocRet,不同的类型可以以不同的方式实现。

与我在评论中的猜测相反,memory所有定义的实例看起来都很好——它们也分配了固定内存。但是该类没有将此作为要求记录,原则上有人可以使用 分配内存Foreign.Marshall.Alloc.malloc,在这种情况下,垃圾收集器不会自动处理内存。如果计算提前中止,这样的假设实现将无法确保释放内存。

于 2020-10-17T04:10:22.893 回答