benjimin
benjimin

Reputation: 4890

When does ctypes free memory?

In Python I'm using ctypes to exchange data with a C library, and the call interface involves nested pointers-to-structs.

If the memory was allocated from in C, then python should (deeply) extract a copy of any needed values and then explicitly ask that C library to deallocate the memory.

If the memory was allocated from in Python, presumably the memory will be deallocated soon after the corresponding ctypes object passes out of scope. How does this work for pointers? If I create a pointer object from a string buffer, then do I need to keep a variable referencing that original buffer object in scope, to prevent this pointer from dangling? Or does the pointer object itself automatically do this for me (even though it won't return the original object)? Does it make any difference whether I'm using pointer, POINTER, cast, c_void_p, or from_address(addressof)?

Upvotes: 3

Views: 2497

Answers (1)

benjimin
benjimin

Reputation: 4890

Nested pointers to simple objects seem fine. The documentation is explicit that ctypes doesn't support "original object return", but implies that the pointer does store a python-reference in order to keep-alive its target object (the precise mechanics might be implementation-specific).

>>> from ctypes import *
>>> x = c_int(7)
>>> triple_ptr = pointer(pointer(pointer(x)))
>>> triple_ptr.contents.contents.contents.value == x.value
True
>>> triple_ptr.contents.contents.contents is x
False
>>> triple_ptr._objects['1']._objects['1']._objects['1'] is x # CPython 3.5
True 

Looks like the pointer function is no different to the POINTER template constructor (like how create_string_buffer relates to c_char * size).

>>> type(pointer(x)) is type(POINTER(c_int)(x))
True

Casting to void also seems to keep the reference (but I'm not sure why it modifies the original pointer?).

>>> ptr = pointer(x)
>>> ptr._objects
{'1': c_int(7)}
>>> pvoid = cast(p, c_void_p)
>>> pvoid._objects is ptr._objects
True
>>> pvoid._objects
{139665053613048: <__main__.LP_c_int object at 0x7f064de87bf8>, '1': c_int(7)}
>>> pvoid._objects['1'] is x
True

Creating an object directly from a memory buffer (or address thereof) looks more fraught.

>>> v = c_void_p.from_buffer(triple_ptr)
>>> v2 = c_void_p.from_buffer_copy(triple_ptr)
>>> type(v._objects)
<class 'memoryview'>
>>> POINTER(POINTER(POINTER(c_int))).from_buffer(v)[0][0][0] == x.value
True
>>> p3 = POINTER(POINTER(POINTER(C_int))).from_address(addressof(triple_ptr))
>>> v2._objects is None is p3._objects is p3._b_base_
True

Incidentally, byref probably keeps-alive the memory it references.

>>> byref(x)._obj is x
True

Upvotes: 2

Related Questions