Co[de]mmunications

Programming and ramblings on software engineering.

The Use of “do { … } While (0)” in C Macros

Consider a countdown program.

countdown.c:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int seconds = 3; 

    for (int i = seconds; i != 0; --i) {
        printf("%i... ", i);
        fflush(stdout);
        sleep(1);
    }
    puts("Go!");

    return 0;
}

The goal is to put the actual countdown code in a macro. It fits perfectly in a function, but a macro is used for illustration purposes. A first attempt at a solution is presented.

solution1.c:

#include <stdio.h>
#include <unistd.h>

#define COUNTDOWN(seconds) \
    for (int i = (seconds); i != 0; --i) { \
        printf("%i... ", i); \
        fflush(stdout); \
        sleep(1); \
    } \
    puts("Go!")

int main(int argc, char **argv)
{
    COUNTDOWN(3);

    return 0;
}

This works fine in the example, but consider what happens when an if clause is added.

Part of solution1-if.c:

    bool start_countdown = false;

    if (start_countdown)
        COUNTDOWN(3);

The output when this program is run is “Go!.” To know what happens, look at the output of gcc -std=c99 -O0 -E solution1-if.c | indent -linux:

int main(int argc, char **argv)
{
    _Bool start_countdown = 0;

    if (start_countdown)
        for (int i = (3); i != 0; --i) {
            printf("%i... ", i);
            fflush(stdout);
            sleep(1);
        }
    puts("Go!");

    return 0;
}

Oops! A second attempt is to wrap the macro in braces.

Part of solution2.c:

#define COUNTDOWN(seconds) \
    { \
        for (int i = (seconds); i != 0; --i) { \
            printf("%i... ", i); \
            fflush(stdout); \
            sleep(1); \
        } \
        puts("Go!"); \
    }

Perfect! This code runs as expected.

Now consider what happens when an else clause is added.

Part of solution2-else.c:

    if (start_countdown)
        COUNTDOWN(3);
    else
        puts("Not ready to start countdown.");

This doesn’t even compile!

$ gcc -std=c99 solution2-else.c
solution2-else.c: In function ‘main’:
solution2-else:21: error: ‘else’ without a previous ‘if’

Again, have a look at the code after macro expansion:

int main(int argc, char **argv)
{
    _Bool start_countdown = 0;

    if (start_countdown) {
        for (int i = (3); i != 0; --i) {
            printf("%i... ", i);
            fflush(stdout);
            sleep(1);
        } puts("Go!");
    };
    else
    puts("Not ready to start countdown.");

    return 0;
}

Notice the badly placed semi-colon. A final solution is presented, which will behave as expected in all situations.

final.c:

#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>

#define COUNTDOWN(seconds) \
    do { \
        for (int i = (seconds); i != 0; --i) { \
            printf("%i... ", i); \
            fflush(stdout); \
            sleep(1); \
        } \
        puts("Go!"); \
    } while (0)

int main(int argc, char **argv)
{
    bool start_countdown = true;

    if (start_countdown)
        COUNTDOWN(3);
    else
        puts("Not ready to start countdown.");

    return 0;
}

After macro expansion:

int main(int argc, char **argv)
{
    _Bool start_countdown = 1;

    if (start_countdown)
        do {
            for (int i = (3); i != 0; --i) {
                printf("%i... ", i);
                fflush(stdout);
                sleep(1);
            } puts("Go!");
        } while (0);
    else
        puts("Not ready to start countdown.");

    return 0;
}

One concern that needs to be addressed is how the seconds argument could be expanded. Furthermore, this technique isn’t the preferable solution most of the time, but if you run into it, you’ll know what it’s usually for. Another use is to prevent a macro to be used as an expression,

#define INCR_EXPR(x) ++(x)
#define INCR_STMT(x) do { ++(x); } while (0)

int main(int argc, char **argv)
{
    int a = 0;
    int b = 0;

    a = INCR_EXPR(b);
    INCR_STMT(b);
    return 0;
}

had it been,

    a = INCR_STMT(b);

gcc would fail with an error “expected expression before ‘do’:”

a = ++(b);
a =
do {
    ++(b);
} while (0);
return 0;

Comments