在 unsafeDupablePerformIO 中分配内存是否安全?

Is it safe to allocate memory within `unsafeDupablePerformIO`?

假设我有一个外部函数:

-- | 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)

(这里我使用的是 memory 中的 alloc。)

我的理解是 unsafePerformIOunsafeDupablePerformIO 的唯一区别是后者的IO动作可以在没有任何清理的情况下静默终止。

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

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

大部分如我在评论中解释的那样,但不完全是:

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 #)
  }}}}}

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

alloc

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

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