释放Haskell中由C运行时分配的内存

5

我正在学习如何使用Haskell的C FFI。

假设我正在调用一个创建对象并返回指向该对象指针的C函数。我可以使用free从Haskell运行时中释放这个内存吗?(我指的是Haskell的free而不是C的free

考虑以下代码:

{-# LANGUAGE ForeignFunctionInterface #-}
module Main where
import Prelude hiding (exp)
import Foreign.Marshal.Alloc
import Foreign.Storable
import Foreign.C.Types
import Foreign.Ptr 
import Foreign.Marshal.Array

foreign import ccall "get_non_freed_array"  c_get_non_freed_array :: CInt -> IO (Ptr CInt) -- An array initialized

main :: IO()
main = do
  let numelements = 5
  ptr <-  c_get_non_freed_array  numelements
  w0  <-  peek $ advancePtr ptr 0 
  w1  <-  peek $ advancePtr ptr 1 
  w2  <-  peek $ advancePtr ptr 2 
  w3  <-  peek $ advancePtr ptr 3 
  w4  <-  peek $ advancePtr ptr 4 
  print [w0, w1, w2, w3, w4]
  return ()

我已经写了一段 C99 中的代码,其中有一个名为 get_non_freed_array 的函数,它的内容如下:

#include "test.h"
#include <stdlib.h>
// return a memory block that is not freed.
int* get_non_freed_array(int n)
{
  int* ptr = (int*) malloc(sizeof(int)*n);

  for(int i=0 ; i<n ; ++i){
          ptr[i] = i*i;
   }
  return ptr;
}

test.h中只包含一个单行,其中包含Haskell的FFI访问get_non_freed_array函数的函数签名。)
我感到困惑,因为我不知道C运行时分配的内存在从Haskell运行时调用后“完成”运行时是否进行垃圾回收。我的意思是,如果是另一个C函数调用它,那么我知道内存将是安全的,但由于Haskell函数调用了get_non_freed_array,我不知道这是否仍然成立。
尽管上述Haskell代码打印出正确的结果,但我不知道通过ptr返回的内存是否安全可用。
如果是安全的,我们可以从Haskell本身释放这个内存吗?还是我必须编写另一个C函数,比如,在test.c中编写destroy_array(int* ptr),然后从Haskell调用它?
编辑:简而言之,我需要更多有关在Haskell中编写代码时如何处理在C函数中创建的对象指针的信息。

你指的是哪个Haskell的free函数?有文档参考吗?编辑:算了,它在这里:http://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/libraries/base-4.9.0.0/Foreign-Marshal-Alloc.html#v:free - Eugene Sh.
http://hackage.haskell.org/package/base-4.9.1.0/docs/Foreign-Marshal-Alloc.html#v:free - smilingbuddha
你应该只使用与分配器用户匹配的free函数来分配内存。即使没有跨越语言障碍,这也适用。C中的不同库可以使用不同的分配器。你需要为所有内容使用正确的free函数。 - Carl
@gspr 我明白了。简而言之,我应该只在Haskell或C的一侧管理分配和释放,对吗? - smilingbuddha
@smilingbuddha:是的。至少在我的经验中,使用Haskell来完成这个任务往往更加美好和干净。alloca等函数和C类型很好用。一旦你开始编写更为复杂的C类型的Haskell版本(用于分配内存),我发现bindings-DSL包可以提供很多帮助(同时在使用上非常简单且没有任何魔法)。 - gspr
显示剩余2条评论
1个回答

2

TL;DR: 使用正确对应的函数(例如C语言中的malloc需要使用C语言中的free)释放内存,如果不可能,请优先考虑使用alloca风格的函数或ForeignPtr


Ptr只是一个地址。通常情况下,Addr#指向垃圾回收机制之外。有了这个知识,我们可以回答你的第一个隐含问题:不,由C运行时分配的内存在C函数结束时不会被垃圾回收。

接下来,在Haskell本身中释放内存通常不安全。你已经使用了C语言的malloc,因此应该使用C语言的free。虽然当前实现中Haskell的free使用了C语言的free,但不能保证以后也会这样,因为Foreign.Marshal.Alloc.free是为Haskell变体设计的。

请注意,我说的是通常情况下。当前GHC实现只使用C语言的对应函数,但不能保证以后也会这样,因此应该使用相应的函数。这对应于你的destroy_array方法:幸运的是,这并不难:

foreign import ccall "stdlib.h free" c_free :: Ptr CInt -> IO ()

您的C文档应包含一条备注,说明free是正确的函数。现在,您可以这样编写您的main函数:
main :: IO()
main = do
  let numelements = 5
  ptr <-  c_get_non_freed_array  numelements
  w0  <-  peek $ advancePtr ptr 0 
  w1  <-  peek $ advancePtr ptr 1 
  w2  <-  peek $ advancePtr ptr 2 
  w3  <-  peek $ advancePtr ptr 3 
  w4  <-  peek $ advancePtr ptr 4 
  print [w0, w1, w2, w3, w4]
  c_free ptr
  return ()

但这和在C语言中一样容易出错。你要求进行垃圾回收,这就是ForeignPtr的用途。我们可以使用newForeignPtr从普通的Ptr创建一个ForeignPtr

newForeignPtr :: FinalizerPtr a -> Ptr a -> IO (ForeignPtr a)

来源 FinalizerPtr (type FinalizerPtr a = FunPtr (Ptr a -> IO ())) 是一个函数指针。因此,我们需要稍微调整之前的导入:

--                                    v
foreign import ccall unsafe "stdlib.h &free" c_free_ptr :: FinalizerPtr CInt
--                                    ^

现在我们可以创建您的数组:
makeArray :: Int -> ForeignPtr CInt
makeArray n = c_get_non_freed_array >>= newForeignPtr c_free_ptr

为了真正使用ForeignPtr,我们需要使用withForeignPtr
main :: IO()
main = do
  let numelements = 5
  fptr <-  makeArray  numelements
  withForeignPtr fptr $ \ptr -> do
      w0  <-  peek $ advancePtr ptr 0 
      w1  <-  peek $ advancePtr ptr 1 
      w2  <-  peek $ advancePtr ptr 2 
      w3  <-  peek $ advancePtr ptr 3 
      w4  <-  peek $ advancePtr ptr 4 
      print [w0, w1, w2, w3, w4]
  return ()
PtrForeignPtr的区别在于后者会调用终结器。但是这个例子稍微有些牵强。如果你只想分配一些空间,对其中的内容进行操作,然后返回结果,那么alloca*函数会使你的生活变得更加轻松,例如:
withArrayLen xs $ \n ptr -> do
   c_fast_sort n ptr
   peekArray n ptr
< p > Foreign.Marshal.* 模块有许多有用的功能。

最后的备注: 使用原始内存可能会很麻烦且容易出错。如果您制作一个供公众使用的库,请将其隐藏。


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