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