【问题标题】:Releasing memory allocated by C-runtime from Haskell从 Haskell 释放 C-runtime 分配的内存
【发布时间】:2017-04-13 15:26:45
【问题描述】:

我正在学习如何使用 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 访问它。)

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

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

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


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

【问题讨论】:

  • 你指的是哪个 Haskell free?你有文档参考吗? Etit:NVM,它是downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/libraries/…
  • 我猜它不安全,因为 C 和 Haskell 的分配算法可能不同。但是你可以试试,如果你幸运的话,你会得到一些分段错误,知道你不能这样做。
  • 您应该只使用与分配器用户匹配的空闲空间来分配内存。即使没有跨越语言障碍,这也适用。 C 中的不同库可以使用不同的分配器。您需要为所有内容使用正确的免费功能。
  • 回答你的最后一个问题(destroy_array):这是一个安全的解决方案,是的。另一种方法是使用alloca(或mallocArray,如果您不希望内存由GHC管理)在Haskell中进行分配,并将指向已分配内存的指针传递给您的C函数(之后将其从Haskell中释放)在mallocArray 情况下;在allocaArray 情况下,您无需再做任何事情)。我经常发现这种方法比在 C 端分配/释放更整洁。

标签: c haskell garbage-collection


【解决方案1】:

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 的用途。我们可以从普通的PtrnewForeignPtr 创建一个:

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

来源 FinalizerPtr(type FinalizerPtr a = FunPtr (Ptr a -&gt; 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.* 模块为此提供了许多有用的功能。

最后的评论:使用原始内存可能会令人讨厌并且是错误的来源。如果您制作一个供公众使用的库,请将其隐藏。

【讨论】:

    猜你喜欢
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-29
    • 2012-01-16
    • 2013-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多