从Haskell中释放由c运行时分配的内存。

时间:2022-02-17 17:04:25

I am learning how to use Haskell's C FFI.

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

Suppose I am calling a C-function which creates an object and then returns a pointer to that object. Am I allowed to free this memory from the Haskell run-time using free? (I am referring to Haskell'sfree not C's free)

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

Consider the following code:

考虑下面的代码:

{-# 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 ()

The get_non_freed_array function I have written in C99 is as follows

我在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 just contains a single line containing the function signature of get_non_freed_array for Haskell's FFI to access it.)

(测试。h只包含一个包含get_non_freed_array函数签名的单行,用于Haskell的FFI访问它。

I am confused because I don't know whether the memory allocated by the C-runtime is garbage collected when that C function "finishes" running after being called from Haskell's run-time. I mean, if it were another C function calling it, then I know the memory would be safe to use, but since a Haskell function is calling get_non_freed_array, I don't know if this is true anymore.

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

Even though the above Haskell code prints correct results, I don't know if the memory returned by the C-function is safe to use via ptr.

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

If it is safe, can we free this memory from Haskell itself? Or do I have to write another C-function, say, destroy_array(int* ptr) inside test.c and then call it from Haskell?

如果它是安全的,我们能把这些记忆从Haskell中解放出来吗?或者我是否必须在测试中编写另一个c函数,比如,驱逐舰_array(int* ptr)。c然后从Haskell调用?


Edit: In short, I need more information on how to work with pointers to objects created inside C-functions when writing code in Haskell.

编辑:简而言之,在使用Haskell编写代码时,我需要更多关于如何使用c -function中创建的对象指针的信息。

1 个解决方案

#1


1  

TL;DR: Deallocate memory with the correct corresponding function (e.g. C's malloc with C's free), and prefer alloca-style functions or ForeignPtr if that's not possible.

TL;DR:用正确的对应函数(例如C的malloc和C的free)来释放内存,如果不可能的话,选择alloca风格的函数或ForeignPtr。


A Ptr is just an address. An Addr# usually points outside of the garbage collected machinery. With this knowledge, we can answer your first implicit question: no, the memory allocated by the C-runtime won't get garbage collected when the C function finishes.

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

Next, it isn't safe in general to free the memory from Haskell itself. You've used C's malloc, so you should use C's free. While the current implementation of Haskell's free uses C's, you cannot count on that, as Foreign.Marshal.Alloc.free is meant for the Haskell variants.

接下来,从Haskell中释放内存是不安全的。您已经使用了C的malloc,所以应该使用C的free。虽然Haskell的免费实现使用了C's,但作为外国人,您不能指望它。

Note that I said in general. The current implementation in GHC uses just the C counterparts, but one should not count on that and instead use the corresponding function. This corresponds to your destroy_array approach: Lucky for us, that's not hard:

注意我说的是一般性的。GHC中的当前实现只使用C对应项,但是不应该指望它,而是使用相应的函数。这对应于你的驱逐舰数组方法:幸运的是,这并不难:

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

Your C documentation should include a remark that free is the correct function though. Now, you could write your main like this:

你的C文档应该包含一个备注,免费是正确的功能。现在,你可以这样写你的主题:

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

But that's just as error prone as in C. You've asked for garbage collection. That's what a ForeignPtr is for. We can create one from a normal Ptr with newForeignPtr:

但这就像c中的错误一样容易发生。这就是外国人的目的。我们可以用newForeignPtr来创建一个:

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

Source The FinalizerPtr (type FinalizerPtr a = FunPtr (Ptr a -> IO ()) is a function pointer. So we need to adjust our previous import slightly:

源FinalizerPtr(类型FinalizerPtr a = FunPtr (Ptr a -> IO()))是一个函数指针。所以我们需要稍微调整一下之前的导入:

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

Now we can create your array:

现在我们可以创建您的数组:

makeArray :: Int -> ForeignPtr CInt
makeArray n = c_get_non_freed_array >>= newForeignPtr c_free_ptr

In order to actually work with the ForeignPtr, we need to use withForeignPtr:

为了与ForeignPtr合作,我们需要使用ForeignPtr:

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

The difference between Ptr and ForeignPtr is that the latter will call a finalizer. But this example is slightly contrived. The alloca* functions make your life a lot easier if you just want to allocate something, work with a function on it, and then return, e.g.

Ptr和ForeignPtr的区别在于后者会调用终结器。但这个例子有点做作。alloca*函数让你的生活变得简单很多,如果你只想分配一些东西,用一个函数处理它,然后返回,例如。

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

The Foreign.Marshal.* modules have many useful functions for that.

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

Final remark: working with raw memory can be a nuisance and an error source. Hide it if you make a library for public use.

最后一点:使用原始内存可能是一个麻烦和错误源。如果你建了一个图书馆供公众使用,就把它藏起来。

#1


1  

TL;DR: Deallocate memory with the correct corresponding function (e.g. C's malloc with C's free), and prefer alloca-style functions or ForeignPtr if that's not possible.

TL;DR:用正确的对应函数(例如C的malloc和C的free)来释放内存,如果不可能的话,选择alloca风格的函数或ForeignPtr。


A Ptr is just an address. An Addr# usually points outside of the garbage collected machinery. With this knowledge, we can answer your first implicit question: no, the memory allocated by the C-runtime won't get garbage collected when the C function finishes.

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

Next, it isn't safe in general to free the memory from Haskell itself. You've used C's malloc, so you should use C's free. While the current implementation of Haskell's free uses C's, you cannot count on that, as Foreign.Marshal.Alloc.free is meant for the Haskell variants.

接下来,从Haskell中释放内存是不安全的。您已经使用了C的malloc,所以应该使用C的free。虽然Haskell的免费实现使用了C's,但作为外国人,您不能指望它。

Note that I said in general. The current implementation in GHC uses just the C counterparts, but one should not count on that and instead use the corresponding function. This corresponds to your destroy_array approach: Lucky for us, that's not hard:

注意我说的是一般性的。GHC中的当前实现只使用C对应项,但是不应该指望它,而是使用相应的函数。这对应于你的驱逐舰数组方法:幸运的是,这并不难:

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

Your C documentation should include a remark that free is the correct function though. Now, you could write your main like this:

你的C文档应该包含一个备注,免费是正确的功能。现在,你可以这样写你的主题:

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

But that's just as error prone as in C. You've asked for garbage collection. That's what a ForeignPtr is for. We can create one from a normal Ptr with newForeignPtr:

但这就像c中的错误一样容易发生。这就是外国人的目的。我们可以用newForeignPtr来创建一个:

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

Source The FinalizerPtr (type FinalizerPtr a = FunPtr (Ptr a -> IO ()) is a function pointer. So we need to adjust our previous import slightly:

源FinalizerPtr(类型FinalizerPtr a = FunPtr (Ptr a -> IO()))是一个函数指针。所以我们需要稍微调整一下之前的导入:

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

Now we can create your array:

现在我们可以创建您的数组:

makeArray :: Int -> ForeignPtr CInt
makeArray n = c_get_non_freed_array >>= newForeignPtr c_free_ptr

In order to actually work with the ForeignPtr, we need to use withForeignPtr:

为了与ForeignPtr合作,我们需要使用ForeignPtr:

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

The difference between Ptr and ForeignPtr is that the latter will call a finalizer. But this example is slightly contrived. The alloca* functions make your life a lot easier if you just want to allocate something, work with a function on it, and then return, e.g.

Ptr和ForeignPtr的区别在于后者会调用终结器。但这个例子有点做作。alloca*函数让你的生活变得简单很多,如果你只想分配一些东西,用一个函数处理它,然后返回,例如。

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

The Foreign.Marshal.* modules have many useful functions for that.

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

Final remark: working with raw memory can be a nuisance and an error source. Hide it if you make a library for public use.

最后一点:使用原始内存可能是一个麻烦和错误源。如果你建了一个图书馆供公众使用,就把它藏起来。