Survival Kit for C Programmers (Macros)

I realized that in my C projects I always need the same macros over and over. I thought it would be interesting to centralize them somewhere and document them.

The macros presented here should work with any recent GCC or LLVM compiler. I do not guarantee they will work on all compiler/architectures.

If you want to go further, I highly recommend you to take a look at the Hedley project. Hedley provides a huge amount of features and is platform generic.

The code for this post (with some additional examples) is available here.

Sizeof Arrays

One lacking feature in C is the ability to retrieve the size of fixed-size arrays. The glorious sizeof operator, returns the full length in bytes of the target object. The COUNT_OF macro shown below solves this issue.

/// Retrieve the number of items in a array.
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))

You can use it as follows:

double local_array[3] = {1.0, 2.0, 3.0};

assert(COUNT_OF(local_array) == 3);
for (int k = 0; k < COUNT_OF(local_array); k++) {
    // do some stuff.
}

If compatibility with C++ is needed, I suggest you pick a safer version of this macro (see this stack overflow post).

Basic Type Generic Operations

MIN, MAX emulates a type generic min and max operations between two variables.

#define MIN(a, b) ((a) > (b)) ? (b) : (a)
#define MAX(a, b) ((a) < (b)) ? (b) : (a)

Another useful one is ABS, which emulates a type generic absolute value.

#define ABS(x) (((x) < 0) ? -(x) : (x))

Another function that appears quite frequently in my code bases is a check for a power of two. It can be implemented with some bit tricks.

#define is_power_of_two(x) ((x) && !((x) & ((x)-1)))

Compiler Hints

There are countless compiler-specific macros that allow to give optimization hints. I only include the ones I use in almost every project.

The most useful ones are LIKELY and UNLIKELY, used to indicate the compiler the expected outcome of a boolean expression.

#define LIKELY(x) __builtin_expect((x), 1)

#define UNLIKELY(x) __builtin_expect((x), 0)

I am not entirely sure that the compiler will always consider those annotations. I generally use them in two situations.

The first situation is in performance critical code, where I want to suggest "really hard" to the compiler what is the hot path in our program. It is worth mentioning that a better approach for large codebases may be to use profile guided optimizations <https://en.wikipedia.org/wiki/Profile-guided_optimization>.

The second situation, is when I am doing error checks. I find those annotations conveniently document the unlikely nature of failures.

int foo_can_fail() {
    if (UNLIKELY(bar_acuda())) {
        return -1;
    }
    if (UNLIKELY(bar_tabac())) {
        return -2;
    }
    return 0;
}

Another macro I use very often is ALIGNED, mostly to align "important" data on cache line boundaries.

#define ALIGNED(x) __attribute__((aligned(x)))

You can use it as follows:

// In variable declaration.
static ALIGNED(16) int array[8] = {};

// In structure definition.
struct coords {
     double x;
     double y;
} ALIGNED(16);

Another powerful macro is ASSUME, we give it predicate that the compiler can exploit in its optimizations phases.

#define ASSUME(cond)                  \
    do {                              \
        if (!(cond))                  \
            __builtin_unreachable();  \
    } while (0)

I typically use this macro to specify constraints on array sizes, to make loop optimizations (unrolling, auto-vectorization) more aggressive. For example:

void add_vec(int* data, size_t data_len, int x) {
    ASSUME(data_len >= 16);
    ASSUME((data_len % 16) == 0);
    for (size_t k = 0; k < data_len; k++) {
        data[k] += x;
    }
}

Here is a side-by-side comparison of the assembly code (gcc -O3) with and without the ASSUME annotation. It is pretty clear that the ASSUME macro allowed to drastically simplify the loop.

Assembly code with and without the ASSUME macro.

But be careful with ASSUME, it is very easy to shoot yourself in the foot with this macro! If the assumption given to the compiler is not true, bad things will happen.

Minimalistic Logging

This topic will be covered in greater details in another post.

I like to have really simple printf-based tracing in my applications (usually enable only in debug builds).

Basically, I write a simple wrapper around fprintf with the following prototype:

int
survival_log(
    long level,
    const char* file,
    const char* func,
    long line,
    const char* fmt,
    ...) __attribute__((format(printf, 5, 6)));

A minimal implementation of this function will look something like this:

#include <stdio.h>
#include <stdarg.h>

int
survival_log(
    long level,
    const char* file,
    const char* func,
    long line,
    const char* fmt,
    ...)
{
    va_list arglist;
    fprintf(
        stderr, "[%ld](%s:%ld in %s) ", level, file, line, func);
    va_start(arglist, fmt);
    vfprintf(stderr, fmt, arglist);
    va_end(arglist);
    return 0;
}

The code may look complex because of variadic arguments, but it is not. What it does is: first call fprintf to print a prefix (log level, file, line) and then forwards the remaining arguments to vfprintf.

Having this survival_log implemented, I define macros that behave as a regular printf. Under the hoods, they just forward their arguments to survival_log.

  • DPRINTF for debug messages.
  • WPRINTF for warning messages.
  • EPRINTF for error messages.

For example DPRINTF can be defined as:

#define DPRINTF(...)                                                         \
    do {                                                                     \
        survival_log(                                                        \
            SURVIVAL_LOG_DEBUG, __FILE__, __func__, __LINE__, __VA_ARGS__);  \
    } while (0)

You can use those macros as a regular printf. For example:

DPRINTF("checking LIKELY, UNLIKELY\n");
if (LIKELY(rnd > 0)) {
    DPRINTF("rand() is > 0, (%d)\n", rnd);
}

Will produce the following output on stderr:

[debug](survival_demo.c:90 in main) checking LIKELY, UNLIKELY
[debug](survival_demo.c:93 in main) rand() is > 0, (1804289383)

This logging system is a very good start for small projects. Despite its simplicity, I find it very effective in practice. Furthermore, it is very easy to extend if needed (e.g., display timestamps, implement filters, log to files, ...).

Scoped Variables

Again, this topic will be covered in greater details in another post.

When possible I like to use the __cleanup__ attribute available in GCC and Clang. This extension allows to implement automatic cleanup of variables (some sort of RAII in C).

Put shortly, if a variable is marked with this attribute:

__attribute__ ((__cleanup__(int_cleanup))) int x = 42;

Then, the function void int_cleanup(int* ptr) will be called, when variable x goes out of scope. The address of x is passed to int_cleanup as first arguments.

So with attribute, you can automatically release pointers, close files, etc.

However, those annotations quickly make code unreadable. Thus, the first thing to do is to simplify the attribute annotation:

#define SURVIVAL_CLEANUP(func) __attribute__((cleanup(func)))

Basically for each type, I define a scoped_[mytype] macro:

#define scoped_int SURVIVAL_CLEANUP(int_cleanup)

Then, I would create scoped variable with this macros:

// __attribute__ ((__cleanup__(int_cleanup))) int x = 42;
// becomes:
scoped_int int x = 42;

We could also think of a scoped_int macro that would expand to __attribute__ ((__cleanup__(int_cleanup))) int. But I like to keep macro expansion straightforward.

Another annoying aspect of the cleanup extension is in the definition of cleanup handlers.

Let's say you want to automatically close a file. You would be tempted to declare your file as:

__attribute__ ((__cleanup__(fclose))) FILE* f = NULL;

However, fclose does not have the correct prototype, because the cleanup extension will pass the address of f (a FILE** type). To automatically generate wrappers I use the following macro (taken from System-D codebase).

#define DEFINE_TRIVIAL_CLEANUP_FUNC(name, type, func)                          \
    static void name(type* p)                                                  \
    {                                                                          \
        if (*p) {                                                              \
            func(*p);                                                          \
        }                                                                      \
    }                                                                          \
    struct __useless_struct_to_allow_trailing_semicolon__

With this macro, you can define a scoped file as follows:

// Instanciate a trivial cleanup function for FILE* types.
DEFINE_TRIVIAL_CLEANUP_FUNC(
    file_cleanup, /* Name for the wrapper generated. */
    FILE*,        /* Type passed to the destructor. */
    fclose        /* Destructor invoked. */
);

// Attribute to declare scoped files.
#define scoped_file SURVIVAL_CLEANUP(file_cleanup)

// Example scoped file:
scoped_file FILE* f = fopen("foo", "r");

Conclusion

I packed the macro described in this post in a header survival.h. You can see them in action in a small demo here.

I would be interested to hear what are your most useful macros. Feel free to PM me or open an discussion on the Github repository.