Mark Unreachable Code in C

Mark Unreachable Code in C

In many programs, control-flow paths are written to satisfy syntactic requirements even when those paths are not expected to be executed in practice. Compilers cannot always deduce that a particular branch is impossible at runtime. As a result, a fallback is often required to keep the program well-formed. When this intent is not expressed explicitly, it may result in less efficient code generation or trigger unnecessary compiler warnings.

Consider a simple example using an enumeration in C:

#include <stdio.h>

enum Color { Red, Green, Blue };

const char *to_string(const enum Color c) {
    switch (c) {
        case Red: return "Red";
        case Green: return "Green";
        case Blue: return "Blue";
        default: return "";
    }
}

int main(void) {
    printf("%s\n", to_string(Red));

    return 0;
}

All valid enumeration values are covered inside the switch statement. In a strictly logical sense, the default branch should never be executed. However, the compiler cannot assume that only valid enum Color values will ever be provided. Because of that uncertainty, a fallback return is typically required.

To express the idea that a code path is not supposed to be reachable, compiler-specific mechanisms are often used. GCC and Clang provide __builtin_unreachable, while MSVC supports __assume(0). When no compiler support is available, aborting the program is a common fallback strategy.

A portable way to express this intent in C is to wrap these mechanisms behind a macro:

#include <stdio.h>

#if defined(__GNUC__)
#define UNREACHABLE() __builtin_unreachable()
#elif defined(_MSC_VER)
#define UNREACHABLE() __assume(0)
#else
#include <stdlib.h>
#define UNREACHABLE() do { abort(); } while(0)
#endif

enum Color { Red, Green, Blue };

const char *to_string(const enum Color c) {
    switch (c) {
        case Red: return "Red";
        case Green: return "Green";
        case Blue: return "Blue";
        default: UNREACHABLE();
    }
}

int main(void) {
    printf("%s\n", to_string(Red));

    return 0;
}

By explicitly marking the default branch as unreachable, the code more clearly communicates its intended behavior at the implementation level. Compilers can also take advantage of this hint, enabling more aggressive optimizations and potentially removing dead code paths entirely under certain conditions.

Leave a Comment

Cancel reply

Your email address will not be published.