Cyclomatic Complexity and Memory Management in C programming

Aniket Pingley, Ph.D.
Techanic
Published in
6 min readApr 7, 2023

In this blog, I address challenges to memory management when code with higher cyclomatic complexity returns from multiple locations inside a function in C programming language. Let us begin with preliminaries about cyclomatic complexity and memory management.

What is cyclomatic complexity?

Cyclomatic complexity is a software metric that looks at how many different paths or points of choice a program has. Counting the amount of if-else statements, loops, switches, and conditional expressions in a program gives it a value. The cyclomatic complexity of a program goes up the more of these things it has.

To put it more simply, cyclomatic complexity is a way to measure how hard it is to use a software system. It is used to figure out how much testing a program needs to make sure it works right. The harder it is to get full code coverage of a program, the more complicated it is in terms of cyclomatic complexity. Code coverage is a way to figure out how much of a program’s source code a test suite runs. To get full code coverage, each line of code needs to be run at least once during the testing process. High cyclomatic complexity means that there are more choice points and paths through the code. This makes it harder to make test cases that cover all possible situations.

In the diagram below, the pseudocode on the left is represented using a graph on the right. Following execution paths can be taken to reach from start (1) to end (8): 1 -> 2 -> 7 -> 8 ### 1 -> 3 -> 4 -> 6 -> 7 -> 8 ### 1 -> 3 -> 5 -> 6 -> 7 -> 8. In simple terms, it means that the execution of all the paths must be tested before the code can into production; lesser the better.

Image source: https://craftofcoding.files.wordpress.com/: Cyclomatic Complexity; Path for execution of code.

Challenges to Memory Management

Memory management is a critical aspect of software development that determines how efficiently an application utilizes system resources. It involves the allocation and deallocation of memory as required by the program during its execution. Memory management plays a significant role in determining the performance of an application. Functions with higher cyclomatic complexity often have multiple return statements that can be reached from different paths. These return statements can make it challenging to manage memory efficiently. If the memory allocated for a particular path is not freed correctly, it can lead to memory leaks and cause the application to consume more memory than necessary, resulting in performance issues.

One of the ways to address the challenge of memory management in functions with higher cyclomatic complexity is to ensure that each return statement frees the memory allocated during its execution. This approach requires careful tracking of memory allocation and deallocation across different paths within the function. In the example below, memory is being freed, wherever needed, at multiple return statements.

Many programmers prefer returning from multiple locations inside a function. However, in the scenarios under discussion, this is big challenge for two reasons: 1) it is relatively more difficult to debug code that returns from multiple locations, and 2) memory management has be performed at every return location (if required).

In programming languages that support exceptions, this can potentially be solved by throwing exceptions, and then managing memory deallocation where the exception is caught. In C programming language exceptions are not a feature. A rudimentary, but highly, effective mechanism is commonly used in commercially written code to address this limitation in C programming. Scroll down further to see the code from the example below being transformed so that both the aforesaid challenges can be mitigated.

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int id;
char* name;
} student;

student* create_student(int id, char* name) {
student* s;
s = (student*) malloc(sizeof(student));
if (s == NULL) {
printf("Error: Failed to allocate memory");
return NULL;
}
if (id < 0) {
printf("Error: ID cannot be negative");
free(s);
return NULL;
}
s->id = id;
s->name = (char*) malloc(sizeof(char) * (strlen(name) + 1));
if (s->name == NULL) {
printf("Error: Failed to allocate memory");
free(s);
return NULL;
}
if (strcmp(name, "") == 0) {
printf("Error: Name cannot be empty");
free(s->name);
free(s);
return NULL;
}
if (id >= 100 && id <= 200) {
if (strlen(name) >= 10 && strlen(name) <= 20) {
strcpy(s->name, name);
return s;
}
else {
printf("Error: Name must be between 10 and 20 characters long");
free(s->name);
free(s);
return NULL;
}
}
else {
printf("Error: ID must be between 100 and 200");
free(s->name);
free(s);
return NULL;
}
}

The do while(0) pattern

The do while(0) pattern is a programming technique used in C and C++ to create a block of code that can be executed multiple times while allowing the use of the break statement to exit the block. This technique can also be used to manage memory when dealing with complex code that has multiple return statements.

The do while(0) pattern works by creating a single block of code that is executed multiple times, allowing the program to allocate and deallocate memory before exiting the block. The use of a single block of code ensures that all allocated memory is deallocated correctly, even when there are multiple return statements inside the function.

In the code below, you will observe how the example from above has be re-implemented to address the challenges presented by having multiple return statements for memory management in code that has higher cyclomatic complexity. The original statements have been retained in form of comments. The code is self-explanatory.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // [FOR BOOL TYPE]

typedef struct
{
int id;
char *name;
} student;

student *create_student(int id, char *name)
{
student *s = NULL;
bool cleanup = true;

do
{
s = (student *)malloc(sizeof(student));
if (NULL == s) // left side of equation should be immutable
{
printf("Error: Failed to allocate memory");
// return NULL; [ORIGINAL]
break;
}
if (id < 0)
{
printf("Error: ID cannot be negative");
// free(s); [ORIGINAL]
// return NULL; [ORIGINAL]
break;
}
s->id = id;
s->name = (char *)malloc(sizeof(char) * (strlen(name) + 1));
if (NULL == s->name) // left side of equation should be immutable
{
printf("Error: Failed to allocate memory");
// free(s); [ORIGINAL]
// return NULL; [ORIGINAL]
break;
}
if (strcmp(name, "") == 0)
{
printf("Error: Name cannot be empty");
// free(s->name); [ORIGINAL]
// free(s); [ORIGINAL]
// return NULL; [ORIGINAL]
break;
}
if (id >= 100 && id <= 200)
{
if (strlen(name) >= 10 && strlen(name) <= 20)
{
strcpy(s->name, name);
// return s; [ORIGINAL]
cleanup = false;
break;
}
else
{
printf("Error: Name must be between 10 and 20 characters long");
// free(s->name); [ORIGINAL]
// free(s); [ORIGINAL]
// return NULL; [ORIGINAL]
break;
}
}
else
{
printf("Error: ID must be between 100 and 200");
// free(s->name); [ORIGINAL]
// free(s); [ORIGINAL]
// return NULL; [ORIGINAL]
break;
}
} while (0); //

if (cleanup)
{
// cleanup at a dedicated sectio
if (NULL != s)
{
free(s->name);
free(s);
s = NULL;
}
}

// return from a single location
// easier to debug
return s;
}

int main() { return 0; }

The use of the do while(0) pattern ensures that all allocated memory is deallocated correctly, regardless of the number of return statements inside the function. This technique can be particularly useful when dealing with complex code that requires extensive error checking and multiple return statements.

In conclusion, managing memory efficiently can be a challenge when dealing with complex code that has multiple return statements inside the function. The do while(0) pattern can be a useful technique for managing memory and ensuring that all allocated memory is deallocated correctly, even when there are multiple return statements. By using this pattern, developers can create more efficient and reliable code that performs well and avoids memory leaks. Happy Coding!

--

--