我正在学习如何使用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
只包含一行包含get_non_freed_array
的函数签名,供Haskell的FFI访问。)
我很困惑,因为我不知道C-runtime分配的内存是否是垃圾收集的,当C函数从Haskell的运行时调用后“完成”运行。我的意思是,如果它是另一个 C 函数调用它,那么我知道内存可以安全使用,但由于Haskell函数调用get_non_freed_array
,我不知道这是否是真的了。
即使上面的Haskell代码打印出正确的结果,我也不知道C函数返回的内存是否可以通过ptr
安全使用。
如果它是安全的,我们可以从Haskell本身释放这些内存吗?或者我是否必须在test.c中编写另一个C函数,例如destroy_array(int* ptr)
,然后从Haskell调用它?
编辑:简而言之,我需要更多关于在Haskell中编写代码时如何使用指向C函数内创建的对象的指针的信息。
答案 0 :(得分:1)
TL; DR:使用正确的相应函数释放内存(例如C的malloc
与C的free
),并且更喜欢alloca
- 样式函数或{ {1}}如果那是不可能的。
ForeignPtr
只是一个地址。 Addr#
通常指向垃圾收集机器之外。有了这些知识,我们就可以回答你的第一个隐含问题:不,C函数分配的内存在C函数完成时不会被垃圾收集。
接下来,一般 是不安全的,从Haskell本身释放内存。您使用过C Ptr
,因此您应该使用C malloc
。虽然Haskell free
的当前实现使用C,但你不能指望它,因为Foreign.Marshal.Alloc.free
是针对Haskell变体的。
请注意,我说一般。 GHC中的当前实现仅使用C对应物,但是不应该依赖于它而是使用相应的功能。这符合您的free
方法:幸运的是,这并不难:
destroy_array
您的C文档应包含foreign import ccall "stdlib.h free" c_free :: Ptr CInt -> IO ()
是正确功能的注释。现在,你可以这样写free
:
main
但这就像C中一样容易出错。你曾要求垃圾收集。这就是ForeignPtr
的用途。我们可以使用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 ()
Ptr
创建一个。{/ 1}
newForeignPtr
来源
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 ()
和Ptr
之间的区别在于后者会调用终结器。但这个例子有点做作。 ForeignPtr
函数可以让你的生活变得更轻松,如果你只想分配一些东西,使用函数,然后返回,例如。
alloca*
withArrayLen xs $ \n ptr -> do
c_fast_sort n ptr
peekArray n ptr
模块有许多有用的功能。
最后评论:使用原始内存可能是一个麻烦和错误来源。如果您将图书馆供公众使用,请将其隐藏。