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
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.