gpt4 book ai didi

c - 从 Haskell 释放 C 运行时分配的内存

转载 作者:太空狗 更新时间:2023-10-29 15:06:49 25 4
gpt4 key购买 nike

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

假设我正在调用一个创建对象然后返回指向该对象的指针的 C 函数。我可以使用 free 从 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 的 free),并且更喜欢 alloca 风格的函数或 ForeignPtr 如果那是不可能的。


Ptr 只是一个地址。一个Addr#通常分外垃圾回收机械。有了这些知识,我们就可以回答您的第一个隐含问题:不,当 C 函数完成时,C 运行时分配的内存不会被垃圾回收。

接下来,从 Haskell 本身释放内存一般是不安全的。你用过 C 的 malloc,所以你应该使用 C 的 free。虽然 Haskell 的 free 的当前实现使用 C,但您不能指望它,因为 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 创建一个:

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

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

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

关于c - 从 Haskell 释放 C 运行时分配的内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43372363/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com