Defining C macros the right way

C is a fascinating programming language. For me, it is a particularly beautiful language, because it gives my blog the right to exist.

The C-standard is fine, but it falls short in many respects. It is a very non-committal standard and leaves a lot to the compiler itself. Large compilers such as gcc and clang have clear and useful specifications for this, which means that as a developer you rarely have any problems with this.

However, there are also things that cannot be solved for you by a clever compiler. That is something you really have to think about for yourself. One of those things: macros.

If you regularly work with C, you are used to macros. Especially if you sometimes look at larger C programmes. The larger a program becomes, the more essential a macro can become.

If you are not familiar with macros: this is how they can be used:

#define macro(x)  foo(x); bar(x)

The consequence of this macro is that you can do this:

macro(arg);

Which results in the following line:

foo(arg); bar(arg);

So far, so good. From here on, however, things are only going downhill. Let’s look at another example:

if (test)
    macro(arg);

This expands into:

if (test)
    foo(arg);
bar(arg);

So now we have a problem. This macro does not behave as you would expect from a function. This is not so strange, because a macro is not a function. All it does is replace text with other text.

We can, of course, place curly braces around our functions to tackle this problem.

#define macro(x)  { foo(x); bar(x); }

Now our problem has been solved! Look what is happening with our previous example!

if (test)
{
    foo(arg);
    bar(arg);
};

However, there are also problems to be imagined in this respect. How about this?

if (test)
    macro(arg);
else
    printf("This failed. :(");

This will result in the following code block:

if (test)
{
    foo(arg);
    bar(arg);
};
else
    printf("This failed. :(");

Because of the }; before else, this is no longer valid code.

So, how do we solve this? We need a way to ensure that everything after the macro identifier is correctly interpreted. Fortunately, there is a way to do that:

#define macro(x) do { foo(x); bar(x); } while(0)

If we use this macro now in our previous example, we get:

if (test)
    do {
        foo(arg); bar(arg);
    } while(0);
else
    printf("This failed. :(");

And with that we have found a solution.

2 thoughts on “Defining C macros the right way

  1. “C is a fascinating programming language. For me, it is a particularly beautiful language, because it gives my blog the right to exist.”

    I can appreciate the refreshing honesty in this. The C language is attractive to some, because it has so many disgusting and unnecessary flaws, so there’s no end to discussing and working around them, and fixing them is neither an option. I prefer to use languages lacking such, so I can focus on what I actually want to program. Of course, the C language is used often in UNIX, where the idea itself behind the program is probably going to be fundamentally misguided anyway.

    The C language communicates far too little information to a compiler, and lacks fundamental characteristics needed for many programs. This doesn’t result in simplicity, but complications borne of convention. In a Lisp program, I can use GENSYM to generate unique names; in a C language program, there’s a convention that’s used; this may be cute for a small program, but there’s such conventions in large programs such as the Linux kernel, which is wholly unreasonable, but the C language is also part of why it’s millions of lines and not as small as it should be.

    Lisp has PROGN for integrating arbitrary forms; PROGN accepts any number of forms and evaluates them in-order, returning the final or nth result, enabling multiple forms in places where only one is accepted, such as a branch of an IF. There’s nothing “fortunate” about the C language being able to use a form of loop run once to achieve this, because it’s clearly happenstance that it works at all; perhaps if it weren’t an option, the C language would be used less. Further, it requires a compiler to recognize and eliminate this inefficiency. It’s not really possible to write a C language program without generating inefficient code, whereas this isn’t the case with Lisp, so a Lisp compiler may also be simpler and smaller than a C language compiler.

    The C language also lacks other fundamental mechanisms, such as UNWIND-PROTECT, which enables one to write code which is always run when a context is escaped, with no chance it won’t be. This is an example of a mechanism which is very useful, but hard or impossible to merely add, rather than integrate from beginnings. Of course, the C language lacks exceptions anyway, and always doing things is, again, handled by mere convention.

    I can’t claim to have actually used Forth for anything, preferring to write machine code directly instead, but it’s a suitable candidate for many instances where the C language is recommended, being simpler and more powerful, so it’s worth glancing at.

    1. Hello Verisimilitude!

      Thank you for your extensive response.

      Forth is something I have never taken a look at. However, it looks very interesting to me.

Leave a Reply

Your email address will not be published. Required fields are marked *