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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With