Skip to main content

KTDEVX

Simplify Memory Management with goto Statements in C

Table of Contents

During development, you may encounter situations where you need to allocate multiple dynamic memory blocks within a function. If memory allocation fails, you’ll need to free any previously allocated memory, which can make error handling complex.

This article explains how to simplify error handling and manage memory deallocation by using the goto statement.

# What is the goto Statement?

The goto statement is a syntax used to jump to a specified label within a function. It is used in the following format:

goto <label-name>;

The label name can be defined anywhere within the function, and the goto statement jumps to that label’s location. Although goto can complicate code flow and reduce readability and maintainability, when used correctly, it can actually improve simplicity, readability, and maintainability.

# Complex Error Handling with Memory Deallocation

Here’s an example of code that manages multiple dynamic memory allocations.

int example(size_t num)
{
    int *p1, *p2, *p3;

    p1 = (int *)malloc(sizeof(int) * num);
    if (p1 == NULL)
    {
        return -1;
    }

    p2 = (int *)malloc(sizeof(int) * num);
    if (p2 == NULL)
    {
        free(p1);
        return -1;
    }

    p3 = (int *)malloc(sizeof(int) * num);
    if (p3 == NULL)
    {
        free(p1);
        free(p2);
        return -1;
    }

    /* Processing using p1, p2, and p3 */

    free(p1);
    free(p2);
    free(p3);

    return 0;
}

While this code works, each memory allocation and deallocation is handled individually, and freeing memory needs to be coded each time an error occurs. This makes the code redundant and increases the risk of memory leaks.

# Simplify Memory Deallocation with goto

Using the goto statement, you can centralize error handling and manage memory deallocation more simply.

int example(size_t num)
{
    int ret = -1;
    int *p1 = NULL, *p2 = NULL, *p3 = NULL;

    p1 = (int *)malloc(sizeof(int) * num);
    if (p1 == NULL)
    {
        goto cleanup;
    }

    p2 = (int *)malloc(sizeof(int) * num);
    if (p2 == NULL)
    {
        goto cleanup;
    }

    p3 = (int *)malloc(sizeof(int) * num);
    if (p3 == NULL)
    {
        goto cleanup;
    }

    /* Processing using p1, p2, and p3 */

    ret = 0;

cleanup:
    free(p1);
    free(p2);
    free(p3);

    return ret;
}

In this example, goto cleanup ensures that all memory is deallocated even if an error occurs. Since the free function can safely take NULL as an argument, this method allows for safe and efficient memory management.

# Conclusion

The goto statement, when misused, can harm readability and maintainability, but using it appropriately can keep code simple. In this example, we demonstrated the benefits of using goto for memory management, but it can also be effective in other situations. It’s essential to use it only in appropriate scenarios.