从Haskell释放由C-runtime分配的内存

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

假设我正在调用一个C函数,它创建一个对象,然后返回一个指向该对象的指针.我是否允许使用免费从Haskell运行时释放此内存?
(我指的是Haskell的免费而不是C的免费)

请考虑以下代码:

{-# 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只包含一行包含get_non_freed_array的函数签名,供Haskell的FFI访问它.)

我很困惑,因为我不知道当C函数从Haskell的运行时调用后“完成”运行时,C运行时分配的内存是否是垃圾收集的.我的意思是,如果它是另一个C函数调用它,那么我知道内存可以安全使用,但由于Haskell函数调用get_non_freed_array,我不知道这是否真的.

即使上面的Haskell代码打印出正确的结果,我也不知道C函数返回的内存是否可以通过ptr安全使用.

如果它是安全的,我们可以从Haskell本身释放这些内存吗?或者我是否必须在test.c中编写另一个C函数,比如destroy_array(int * ptr),然后从Haskell中调用它?

编辑:简而言之,我需要更多关于如何在Haskell中编写代码时使用指向C函数内创建的对象的指针的信息.

最佳答案 TL; DR:使用正确的相应函数释放内存(例如C的malloc,C是免费的),如果不可能,则更喜欢alloca风格的函数或ForeignPtr.

Ptr只是一个地址. Addr#通常指向垃圾收集机器之外.有了这些知识,我们就可以回答你的第一个隐含问题:不,C函数分配的内存在C函数完成时不会被垃圾收集.

接下来,从Haskell本身释放内存通常是不安全的.你使用过C的malloc,所以你应该免费使用C语言.虽然目前Haskell的免费使用C实现,但你不能指望它,因为Foreign.Marshal.Alloc.free是针对Haskell变体的.

请注意,我一般说. GHC中的当前实现仅使用C对应物,但是不应该依赖于它而是使用相应的功能.这对应于你的destroy_array方法:幸运的是,这并不难:

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

您的C文档应该包含一条注释,即free是正确的功能.现在,你可以这样写你的主要:

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创建一个:

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

资源
FinalizerPtr(类型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 ()

Ptr和ForeignPtr之间的区别在于后者将调用终结器.但这个例子有点做作.如果你只想分配一些东西,使用函数处理它,然后返回,例如,alloca *函数可以让你的生活更轻松.

withArrayLen xs $\n ptr -> do
   c_fast_sort n ptr
   peekArray n ptr

Foreign.Marshal.*模块具有许多有用的功能.

最后评论:使用原始内存可能是一个麻烦和错误来源.如果您将图书馆供公众使用,请将其隐藏.

点赞