[Pyrex] Python Strings and PyMem_Malloc/Free

len-l at telus.net len-l at telus.net
Thu Jun 30 22:29:06 CEST 2005


On 30 Jun 2005 at 16:06, Grant McDonald wrote:

> 
> Hi, 
> I fear I may have been operating under a false assumption with regards to creating Python 
> strings. This assumption goes like this:
> 1) Whatever memory you PyMem_Malloc you should always PyMem_Free.

Correct. These functions operate below Python's garbage collector.

> 2) For strings you malloc and need to turn into python strings assign them to a python variable in 
> Pyrex then PyMem_Free the pointer to the string before you return the python object.
> 3) This shouldn't present a problem because PyString_FromString copies the string to an internal 
> representation (this is my assumption).

Correct. PyString_FromString does copy the C string rather than just keep a pointer 
to it.

> I believe the last statement may be completely erroneous :). Here is some code based upon this 
> assumption: 
> cdef class Interface: 
[snip]
>  . 
>  def GetNote(self): 
>  cdef TRK_UINT bufferSize, remaining, max, titleSize, result 
>  cdef LPSTR buffer, title, temp_buffer 
>  
>  result = TrkGetNoteDataLength(self.trkNoteHandle, &bufferSize) 
>  if result != TRK_SUCCESS: 
>  raise ExtensionExceptions.TrackerError, result 
>  
>  buffer = <LPSTR>PyMem_Malloc(sizeof(char)*bufferSize) 
>  if buffer == NULL: 
>  raise Exception, "Could not malloc %d bytes!!" % bufferSize 
>  temp_buffer = <LPSTR>PyMem_Malloc(sizeof(char)*bufferSize) 
>  if temp_buffer == NULL: 
>  raise Exception, "Could not malloc %d bytes!!" % bufferSize 
>  memset(buffer, 0x00, bufferSize) 
>  memset(temp_buffer, 0x00, bufferSize) 
>  
>  if bufferSize > 10000: 
>  max = 10000 
>  else: 
>  max = bufferSize 
>  
>  remaining = bufferSize 
>  pyNote = '' 
>  
>  while remaining != 0: 
>  result = TrkGetNoteData(self.trkNoteHandle, max, temp_buffer, &remaining) 
>  if result != TRK_SUCCESS and result != TRK_E_DATA_TRUNCATED: 
>  PyMem_Free(buffer) 
>  PyMem_Free(temp_buffer) 
>  raise ExtensionExceptions.TrackerError, result 
>  strcat(buffer, temp_buffer) 
>  memset(temp_buffer, 0x00, bufferSize) 
>  
>  titleSize = 200 
>  
>  title = <LPSTR>PyMem_Malloc(sizeof(char)*titleSize) 
>  if title == NULL: 
>  raise Exception, "Could not malloc %d bytes!!" % titleSize 
>  memset(title, 0x00, titleSize) 
>  
>  result = TrkGetNoteTitle(self.trkNoteHandle, titleSize, title) 
>  if result != TRK_SUCCESS: 
>  PyMem_Free(buffer) 
>  PyMem_Free(temp_buffer) 
>  PyMem_Free(title) 
>  raise ExtensionExceptions.TrackerError, result 
>  
>  pyTitle = title 
>  pyNote = buffer 
>  
>  # it is this code that appears to be problematic 
>  PyMem_Free(buffer) 
>  PyMem_Free(temp_buffer) 
>  PyMem_Free(title) 
>  
>  return (pyTitle,pyNote) 
> As the code stands calling this function _sometimes_ crashes the Python interpreter (usually the 
> 3rd or 5th call) with an access violation (mem address 0x0000000 which would imply a null 
> pointer reference). If I comment out the final three calls to PyMem_Free then the problem does 
> not arise.
> If my assumption is wrong does the same hold true for structures? I have similar malloc/free 
> calls for structures which save data from them and then free the structure memory before 
> returning.

How are you declaring PyMem_Malloc / Free? It should be:

cdef extern from "python.h":
    void *PyMem_Malloc(unsigned int n)
    void PyMem_Free(void *p)

Anyway, if that is not the problem just try using malloc and free directly. Since you 
are managing the memory assigned to buffer, title and temp_buffer then it can be in 
a separate heap from that used by the interpreter.

A final suggestion. Wrap the body of GetNote within a try-finally statement with the 
PyMem_Free (or free) calls in the finally block. Then buffer, temp_buffer, and title 
are guaranteed to be freed when a value is returned or an exception is raised. Just 
make sure there are no PyMem_Free (free) calls in the try block.

def GetNote(self):
    ...
    cdef LPSTR buffer, title, temp_buffer
    buffer = NULL
    title = NULL
    temp_buffer = NULL
    try:
        ...
        if result != TRK_SUCCESS:
            raise ExtenionExceptions.TrackerError, result
        ...
        pyTitle = title
        pyNote = buffer
        return (pyTitle, pyNote)
    finally:
        # Garbage collection
        PyMem_Free(buffer) # or free(buffer)
        PyMem_Free(title)
        PyMem_Free(temp_buffer)

Lenard Lindstrom
<len-l at telus.net>




More information about the Pyrex mailing list