irrelevant


"Safe" memory allocation utilities (redux)

This is an update/rewrite of my previous post on this.

Typically, I do not really care if my memory allocation fails or not. I just want to assume it will succeed and I intentionally cause it to abort() on failure.

My standard library of allocation routines starts with the xmalloc(). A lot of other libraries and applications does the same.

It is implemented like so

static inline void *xmalloc(size_t sz)
{
        void *mem;

        if (unlikely(!sz))
                return NULL;

        mem = malloc(sz);
        if (unlikely(!mem))
                backtrace_abort();

        return mem;
}

The unlikely() is a pretty standard macro by the way.

#define unlikely(cond) __builtin_expect(!!(cond), 0)

backtrace_abort() aborts and if running using glibc, it also prints a helpful backtrace.

#ifdef __GLIBC__
#include <execinfo.h>
#endif

void backtrace_abort(void)
{
#ifdef __GLIBC__
        void *buf[10];
        char **symbols = NULL;
        int size;

        size = backtrace(buf, 10);
        symbols = backtrace_symbols(buf, size);

        if (symbols) {
                fprintf(stderr, "fatal error; dumping at most %d stack frames\n", size);

                for (int i = 0; i < size; i++)
                        fprintf(stderr, "[%d]: %s\n", i, symbols[i]);
        }

        free(symbols);
#endif /* __GLIBC__ */
        abort();
}

There is a zmalloc() as well that just uses calloc(1, sz) instead to zero the memory. However, I rarely use these directly. Instead, I typically allocate memory for quantity of something. When that something is just a number of bytes, I use a set of n-suffixed versions.

static inline void *mallocn(unsigned int n, size_t sz)
{
        if (would_overflow(n, sz)) {
                fprintf(stderr, "allocation of %d * %zu bytes would overflow\n", n, sz);

                backtrace_abort();
        }

        return xmalloc(n * sz);
}

static inline void *zmallocn(unsigned int n, size_t sz)
{
        if (would_overflow(n, sz)) {
                fprintf(stderr, "allocation of %d * %zu bytes would overflow\n", n, sz);

                backtrace_abort();
        }

        return zmalloc(n * sz);
}

static inline void *reallocn(void *mem, unsigned int n, size_t sz)
{
        if (would_overflow(n, sz)) {
                fprintf(stderr, "allocation of %d * %zu bytes would overflow\n", n, sz);

                backtrace_abort();
        }

        return realloc(mem, n * sz);
}

For these allocation it is crucial that n times sz does not overflow, so they all verify that it does not.

static inline bool would_overflow(unsigned int n, size_t sz)
{
        return n > SIZE_MAX / sz;
}

Finally, when the something is a type, I use a set of new macros that are inspired by those in glib. This allows me to do something like void *foos = znew_t(struct foo, 42);.

#define _new_t(t, n, f) \
        ((t *) f(n, sizeof(t)))

#define new_t(t, n) _new_t(t, n, mallocn)
#define znew_t(t, n) _new_t(t, n, zmallocn)

I've already mentioned this in another post, but since it is so useful, let me repeat it here for posterity.

static inline void __do_autofree(void *mem)
{
        void **memp = (void **)mem;

        free(*memp);
}

#define __autofree __attribute__((cleanup(__do_autofree)))

Mark your pointers __autofree and watch them be free'ed when going out of scope. Hurrah!