Search code examples
pythoncpython-c-api

Best/Cleanest way to structure error checking on dynamically allocated variables


I am working on a Python C project that involves a couple of dynamically allocated structures like queues and threading structures. I am mainly looking to improve the readability. It looks something like this:

#include <Python.h>
#include <stdlib.h>

typedef struct Queue{
   void *data;
} Queue;
typedef struct Control{
   void *data;
} Control;
typedef struct Logging{
   void *data;
} Logging;
typedef struct Args{
   Queue *q;
   Control *c;
   Logging *l;
   void *data;
} Args;

void cleanupQueue(Queue *q);
void cleanupControl(Control *c);
void cleanupLogging(Logging *l);
void cleanupArgs(Args *a);
static PyObject *pycalledfunction(){
  Queue *queue = (Queue *) malloc(sizeof(Queue));
  if(!queue){
    PyErr_SetString(type, message);
    return NULL;
  }
  // initialize queue and fill
  
  Control *control = (Control *) malloc(sizeof(Control));
  if(!control){
    cleanupQueue(queue);
    PyErr_SetString(type, message);
    return NULL;
  }
  // initialize control
  
  Logging *logging = (Logging *) malloc(sizeof(Logging));
  if(!logging){
    cleanupControl(control);
    cleanupQueue(queue);
    PyErr_SetString(type, message);
    return NULL;
  }
  // initialize logging
  
  Args *args = (Args *) malloc(sizeof(Args));
  if(!logging){
    cleanupLogging(logging);
    cleanupControl(control);
    cleanupQueue(queue);
    PyErr_SetString(type, message);
    return NULL;
  }
  // initialize and fill args with the other structs
  
  // Note: I guess I could check if they all exist before doing work with them here but I was unsure if that is a good practice since I am loading them into the args and am keen on getting the correct error message.
  
  // bunch of work here
  
  cleanupQueue(queue);
  cleanupControl(control);
  cleanupLogging(logging);
  cleanupArgs(args);
  return obj;
}

Now this code works just fine as long as it cleans up the allocated memory properly; although it lowers the flow of the code and its overall readability by a small margin, I am still looking to improve my structuring practices by any margin. I left a note in there marking that I could check each of the structures before doing any work(having all of the checking in the same place), but I feel as if this would result in the same flow.

Is there any way to do this in a way that ensures that I return the correct error while improving the structure of my code?

EDIT: Decided that goto statements are sufficient enough to clean the code enough(while maintaining good practice). If there are any other methods that you believe clean the code further, send me a message to reopen.

EDIT: Note of semi-duplicate to this question


Solution

  • You can pull out good ol' goto (this is one valid use-case of goto):

    static PyObject *pycalledfunction()
    {
      Queue *queue = malloc(sizeof *queue);
    
      if (!queue){
          goto fail_queue;
      }
      
      Control *control = malloc(sizeof *control);
    
      if (!control){
          goto fail_control;
      }
      
      Logging *logging = malloc(sizeof *logging);
    
      if (!logging){
          goto fail_logging;
      }
      
      Args *args = malloc(sizeof *args);
    
      if (!args){
          goto fail_args;
      }
    
      cleanupQueue(queue);
      cleanupControl(control);
      cleanupLogging(logging);
      cleanupArgs(args);
      return obj;
      
    fail_args:
      cleanupLogging(logging);
    fail_logging:
        cleanupControl(control);
    fail_control:
        cleanupQueue(queue);
    fail_queue:
        PyErr_SetString(type, message);
        return NULL;
    }
    

    My labels may not be creative enough, rename them however you may. Or use the nested approach that @Jonathan Leffer shows here: https://stackoverflow.com/questions/788903/valid-use-of-goto-for-error-management-in-c.