The Developer’s Cry

Yet another blog by a hobbyist programmer

Of Bitfields and Booleans

Bit twiddling is an important skill that any programmer should have. It allows you to manipulate the smallest entity in computer memory: a single bit. You can do power-of-two arithmetics by bit shifting, and you can use the XOR bit operation for encryption. But much easier than that, a single bit may represent a flag; it is either set or clear.

Modern C includes a bool type and I absolutely love it. I use it especially as return type for functions to indicate success (true) or failure (false), where classic C would use int (zero for success, versus minus one for failure). As a practical implementation, a bool typically compiles to either a 32-bit or an 8-bit integer.

Since like forever, I have always coded bit flags in C using #define, like so:

// window flags
#define WF_ACTIVE       1
#define WF_BORDER       2
#define WF_HIDDEN       4
#define WF_FOCUS        8
#define WF_NEED_REDRAW  0x10
#define WF_SOMETHING    0x20

...

if (window->flags & WF_HIDDEN) {
    return;
}
if (window->flags & WF_FOCUS) {
    window_redraw(window);
}

A flag is a single bit, and therefore the naturally right thing to do is to (manually) pack multiple flags together in a single integer. I’m not using bool in structures when I don’t have to; this saves a couple of bytes of memory. It may not seem like much, but every little bit helps (pun intended).

Defines are macros, not very elegant, so we might also use enum:

typedef enum {
    WF_NONE,
    WF_ACTIVE = 1,
    WF_BORDER = 2,
    WF_HIDDEN = 4,
    WF_FOCUS = 8,
    WF_NEED_REDRAW = 0x10
} WindowFlag;

The nice thing about enum is that the flags are now typed. This is entirely theoretical in this case however, because these symbols will only ever be used as int. There is something about using enum for bit flags that doesn’t quite mesh with me; if a WindowFlag is a strong type that is a single bit, then how can they possibly be combined into an integer flags field? Anyway, on to the next point.

Now comes along a clever guy (namely, me!) who says, “in modern C you can use bitfields, which is much nicer”.

typedef struct {
    int is_active:1;
    int has_border:1;
    int is_hidden:1;
    int has_focus:1;
    int need_redraw:1;

    // and some other members here
    // that don't matter for this post

} Window;

Now we can write our code like this:

if (window->is_hidden) {
    return;
}
if (window->has_focus) {
    redraw_focus(window);
}

Indeed, big improvement. Kudos!

But then there was something fishy going on. The program seemed to misbehave. A glitch? What was happening here?

void window_set_focus(Window* w, bool focus) {
    if (w->has_focus != focus) {
        window_focus_changed(w);
    }
    w->has_focus = focus;
}

When you read this code, you expect that the focus_changed event only occurs when the focus is different; you can set focus and the event should occur if it didn’t have focus before. If the window already had focus, the event should not occur.

NOT SO. It doesn’t work.

The problem with this little snippet is the comparison between the bitfield and a boolean … I was ready to scream “compiler bug!”, but the sad fact is that a bitfield of one is not the same thing as a boolean in the C programming language.

A bitfield of one is not the same thing as a boolean.

The debugger reveals that bitfield has_focus equals (int)-1, while bool focus equals 1. Both are compiled down to 32-bit integers, and both are used here as truth value. But they are not the same. I feel like the computer is boldly telling us "TRUE is not equal to True".

Ugh. I call it a design flaw, but it’s clear that the language works as designed; the bitfield is a signed integer consisting of exactly one bit, which is indeed (int)-1. The bool on the righthand side gets an implicit type conversion to (int)1.

The bool in C is a broken type because you can not use it in boolean operations with integers. What happens instead is that the boolean operation gets silently upgraded to an integer operation. It’s just … wrong. You get no compiler warning, not even with -wconversion enabled.

I guess I should have known. C always had a reputation for doing implicit type conversions.