Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Releasing memory allocated by C-runtime from Haskell

I am learning how to use Haskell's 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)

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

#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.)

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.

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.

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?


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

like image 329
smilingbuddha Avatar asked Oct 29 '22 09:10

smilingbuddha


1 Answers

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.


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.

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.

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:

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:

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:

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:

--                                    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:

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.

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

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

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

like image 68
Zeta Avatar answered Nov 15 '22 08:11

Zeta