CleanUp when something goes wrong

Category: c/c++ tips
Tags: C

Disclaimer

  1. I Need to give credit to some guy on the internet who inspired me to use this method. I can't find the source any more (sorry).

  2. YMMV, but I like this method. I addmit it is a bit tedious but it gives me some sense of control.

Background

Sometimes you are doing stuff in plain C, when you know that things can go wrong. And when It goes wrong, you should cleanup but that is a chore that sucks.

The setup

file: common.h

#ifndef common_h__
#define common_h__

/* EVENT */
struct event_t {
    int dummy;
};
struct event_t * event_init(const int c);
void event_cleanup(struct event_t * events);

/* ASSETS */
struct asset_t {
    int dummy;
};
struct asset_t * asset_init(const int v);
void asset_cleanup(struct asset_t * assets);

/* FLUBBER */
struct flubber_t {
    int ok;
};
struct flubber_t * flubber_init(const struct event_t* event, struct asset_t* assets);
void flubber_cleanup(struct flubber_t * flubber);

/* ZED */
struct zed_t {
    int dummy;
};

struct zed_t * zed_init(struct flubber_t * flubber);
struct zed_t * zed_cleanup(struct zed_t * zed);
void zed_do_stuff(struct zed_t * zed);

int world_domination(const int c, const int v);

#endif

The naive method

Let it crash on error and without errors, the os should clean up.

file: naive.c

#include "common.h"

int world_domination(const int c, const int v) {
    struct event_t * event;
    struct asset_t * asset;
    struct flubber_t * flubber;
    struct zed_t * zed;

    event = event_init(c);
    asset = asset_init(v);
    flubber= flubber_init(event, asset);
    while (flubber->ok) {
        zed = zed_init(flubber);
        zed_do_stuff(zed);
        zed_cleanup(zed);
    }
    //let the operating system clean the memory

    return 0;
}

When any of this fails, we'll get in trouble later.

The slightly better method

file: slightly.c

#include <stdlib.h>

#include "common.h"

int world_domination(const int c, const int v) {
    struct event_t * event;
    struct asset_t * asset;
    struct flubber_t * flubber;
    struct zed_t * zed;
    int status = 0;

    event = event_init(c);
    if (event == NULL) {
        asset = asset_init(v);
        if (asset != NULL) {
            flubber= flubber_init(event, asset);
            if (flubber != NULL) {
                while (flubber->ok) {
                    zed = zed_init(flubber);
                    if (zed != NULL ) {
                        zed_do_stuff(zed);
                        zed_cleanup(zed);
                    }
                }
                flubber_cleanup(flubber);
            } else {
                status = 1;
            }
            asset_cleanup(asset);
        } else {
            status = 1;
        }
        event_cleanup(event);
    } else {
        status = 1;
    }

    return status;
}

This pyramid is less readable and probably hard to maintain.

The slightly better flow method

file: flow.c

#include <stdlib.h>

#include "common.h"


int world_domination(const int c, const int v) {
    struct event_t * event;
    struct asset_t * asset;
    struct flubber_t * flubber;
    struct zed_t * zed;

    event = NULL;
    asset = NULL;
    flubber = NULL;
    zed = NULL;

    event = event_init(c);
    if (event == NULL) {

        return 1;
    }
    asset = asset_init(v);
    if (asset == NULL) {
        event_cleanup(event);

        return 1;
    }
    flubber = flubber_init(event, asset);
    if (flubber == NULL) {
        asset_cleanup(asset);
        event_cleanup(event);

        return 1;
    }
    while (flubber->ok) {
        zed = zed_init(flubber);
        if (zed != NULL) {
            zed_do_stuff(zed);
            zed_cleanup(zed);
        }
    }

    flubber_cleanup(flubber);
    asset_cleanup(asset);
    event_cleanup(event);

    return 0;
}

This flow is a little better readable but is still hard to maintain.

My preferred method

file: prefered.c

#include <string.h>
#include <stdlib.h>

#include "common.h"

int world_domination(const int c, const int v) {
    struct event_t * event;
    struct asset_t * asset;
    struct flubber_t * flubber;
    struct zed_t * zed;
    struct cleanUp {
        unsigned int good:1;
        unsigned int event:1;
        unsigned int asset:1;
        unsigned int flubber:1;
        unsigned int zed:1;
    } cleanUp;

    memset(&cleanUp, 0, sizeof(cleanUp));
    event = NULL;
    asset = NULL;
    flubber = NULL;
    zed = NULL;
    cleanUp.good = cleanUp.event = ((event = event_init(c)) != NULL);
    if (cleanUp.good) {
        cleanUp.good = cleanUp.asset = ((asset = asset_init(v)) != NULL);
    }
    if (cleanUp.good) {
        cleanUp.good = cleanUp.flubber = ((flubber = flubber_init(event, asset)) != NULL);
    }
    if (cleanUp.good) {
        while (flubber->ok) {
            cleanUp.good = cleanUp.zed = (( zed = zed_init(flubber) ) != NULL);
            if (cleanUp.good) {
                zed_do_stuff(zed);
                zed_cleanup(zed);
            }
        }
    }
    if (!cleanUp.good) {
        if (cleanUp.flubber) {
            flubber_cleanup(flubber);
        }
        if (cleanUp.asset) {
            asset_cleanup(asset);
        }
        if (cleanUp.event) {
            event_cleanup(event);
        }
    }
    return (cleanUp.good) ? 0: 1;
}

This keeps the flow forward with a clean exit.

Try it your self:

file: Makefile.mk

CC = clang

all = ./tmp/flow.o ./tmp/naive.o ./tmp/prefered.o ./tmp/slightly.o

./tmp:
    mkdir -p $@

./tmp/flow.o: flow.c common.h ./tmp
    $(CC) -c -Wall -Wextra -Weverything -o $@ $<

./tmp/naive.o: naive.c common.h ./tmp
    $(CC) -c -Wall -Wextra -Weverything -o $@ $<

./tmp/prefered.o: prefered.c common.h ./tmp
    $(CC) -c -Wall -Wextra -Weverything -wno-padded -o $@ $<

./tmp/slightly.o: slightly.c common.h ./tmp
    $(CC) -c -Wall -Wextra -Weverything -o $@ $<

.PHONY: clean

clean:
    rm -rf tmp

make -f Makefile.mk