Search code examples
pythoncmemory-managementfreepython-cffi

Do I need to free memory returned from a C function called via CFFI?


I have this example code that has a function text() returning a newly allocated string:

ffi_test = FFI()
ffi_test.set_source('_test', '''
char* test() { return strdup("hello world"); }
''')
ffi_test.cdef('''
char* test();
void free(void *);
''')
ffi_test.compile(verbose=True)

This works fine:

In [1]: from _test import ffi, lib
In [2]: x = lib.test()
In [3]: ffi.string(x)
Out[3]: b'hello world'
In [4]: lib.free(x)

However, I could not find anything in the docs whether I actually need to manually free() the returned string of if CFFI takes ownership of the pointer as soon as it's returned to Python code.

Also, if I do need to manually free() it, do I need to expose free() in my cdefs or is does CFFI provide some nicer way for it?


Solution

  • From the documentation on Working with pointers, structures and arrays, and quoting the correct section:

    Any operation that would in C return a pointer or array or struct type gives you a fresh cdata object. Unlike the “original” one, these fresh cdata objects don’t have ownership

    Therefore you must free it, there is no way it can assume the ownership: In C there are many functions that return a pointer to a constant string in memory, that not only it is not dynamically allocated, it is not allocated, or modifiable at all, for example. Freeing those would be very erroneous.


    Also for free, the documentation says the following:

    An alternative is to declare and call the C malloc() and free() functions, or some variant like mmap() and munmap(). Then you control exactly when the memory is allocated and freed. For example, add these two lines to your existing ffibuilder.cdef():

    void *malloc(size_t size);
    void free(void *ptr);
    

    Since it is very important that the correct C standard library free is used for a pointer returned by strdup you cannot rely CFFI to magically do the correct thing, but instead as you suspected, you should expose free(). You can also use the gc as suggested by Barmar to register an automatic clean up, if needed:

    x = ffi.gc(x, lib.free)