Wrapping Multiline Macros in C With Do/While

Understanding Multiline Macros

This blog post describes logical errors that can occur with multiline macros in C programming, and demonstrates how to resolve them directly in the definition of the macro itself. After reading this post, you should understand why wrapping multiline C macros with a “do/while” statement is a best practice to avoid these logical errors.

Macros in C

Introductory programming in C presents to students a concept that sort of resembles a function: macros. The preprocessor expands macros in place when code is compiled. So, for example, the following code:

#include <stdio.h>
#define INCREMENT(a) a++

int main()
{
  int x = 2;
  INCREMENT(x);
  printf("%d\n", x);
  return 0;
}

gets translated by the preprocessor to be identical to the following code:

#include <stdio.h>

int main()
{
  int x = 2;
  x++;
  printf("%d\n", x);
  return 0;
}

Both of these short programs output the number 3.

Problems with Multiline Macros

The fact that the preprocessor expands macros in place can lead to some unexpected behaviour with multiline macros. Consider the following code:

#include <stdio.h>
#define INCREMENT_BOTH(a, b) \
a++;                         \
b++

int main()
{
  int x = 2;
  int y = 5;
  int true_false_var = 0;
  if (true_false_var)
    INCREMENT_BOTH(x, y);
  printf("%d %d\n", x, y);
  return 0;
}

For a student first learning C programming, the very reasonable expectation is that this code would output the numbers 2 and 5. After all, the variable true_false_var is set to a false value. However, the above code doesn’t behave as expected.

The critical concept about macros in C is that the preprocessor expands them in place. So, the above code is identical to the following:

#include <stdio.h>

int main()
{
  int x = 2;
  int y = 5;
  int true_false_var = 0;
  if (true_false_var)
    x++;
  y++;
  printf("%d %d\n", x, y);
  return 0;
}

Both of these programs output the numbers 2 and 6. While we added the indentation in the second program for emphasis, the C programming language doesn’t care about indentation. The incrementation of the variable “y” happens outside of the scope of the “if” statement.

Just Use Braces?

Of course, a student learning C programming might ask at this point, why not just use braces with your “if” statements? That is to say, write the following:

#include <stdio.h>
#define INCREMENT_BOTH(a, b) \
a++;                         \
b++

int main()
{
  int x = 2;
  int y = 5;
  int true_false_var = 0;
  if (true_false_var) {
    INCREMENT_BOTH(x, y);
  }
  printf("%d %d\n", x, y);
  return 0;
}

Note the braces around the contents of the “if” statement. With these braces, the code above behaves as expected: it outputs the numbers 2 and 5.

Personally, I feel it’s best practice to wrap the contents of all “if” statements in braces, as I’ve shown above (it’ll save us a bundle of time hunting down programming errors when we’re revising our code!). However, with the macro written as above, we’re counting on whoever uses the macro — likely, you — to remember to wrap the contents of any “if” statements in braces. Maybe that isn’t the coding style you use. Maybe you’ll forget to do it sometimes.

Macros with do/while Wrapping

A best practice for avoiding logical errors with multiline macros is to wrap their contents in a “do/while” statement with a false value on the “while“. Rewriting the multiline macro as above with a “do/while” statement looks like the following:

#include <stdio.h>
#define INCREMENT_BOTH(a, b) \
do {                         \
  a++;                       \
  b++;                       \
} while (0)

int main()
{
  int x = 2;
  int y = 5;
  int true_false_var = 0;
  if (true_false_var)
    INCREMENT_BOTH(x, y);
  printf("%d %d\n", x, y);
  return 0;
}

The preprocessor expands the macro in place to the following:

#include <stdio.h>

int main()
{
  int x = 2;
  int y = 5;
  int true_false_var = 0;
  if (true_false_var)
    do {
      x++;
      y++;
    } while(0);
  printf("%d %d\n", x, y);
  return 0;
}

The compiler will optimize out the pointless “do/while” computation, but the code will still behave as expected: it’ll output the numbers 2 and 5.

No Trailing Semicolon

When wrapping multiline macros in a “do/while” statement, it’s important not to place a semicolon after the “while“. That is, we wrote this code:

#define INCREMENT_BOTH(a, b) \
do {                         \
  a++;                       \
  b++;                       \
} while (0)

We didn’t write the following:

#define INCREMENT_BOTH_WRONG(a, b) \
do {                               \
  a++;                             \
  b++;                             \
} while (0);

The reason for doing this is twofold. If we consider macros as something akin to functions, we want the following to produce a compiler error (because there is no semicolon after the macro usage):

if (true_false_var)
  INCREMENT_BOTH_WRONG(x, y)
printf("%d %d\n", x, y);

If the macro includes a semicolon, the preprocessor expands the macro usage to include that semicolon, and the above code will compile without error.

Additionally, if we include a semicolon in the macro definition, then the following code (which we would expect to compile without error) produces a compiler error:

if (true_false_var)
  INCREMENT_BOTH_WRONG(x, y);
else
  printf("Hello world\n");
printf("%d %d\n", x, y);

With a semicolon included at the end of the macro definition, the “second” semicolon (on the same line as “INCREMENT_BOTH_WRONG“) terminates the scope of the “if” statement, turning the “else” statement into a floating “else” that doesn’t correspond to an “if“.

I encourage students to try the above code examples using both “INCREMENT_BOTH” without the trailing semicolon and “INCREMENT_BOTH_WRONG” with the trailing semicolon to see both of these issues with “INCREMENT_BOTH_WRONG“.

Summary

When using multiline macros in C, the best practice for avoiding logical errors is to place a “do/while” statement with a false value on the “while” in the macro definition itself. By doing so, our macros will behave like functions, and we can avoid logical errors when using them inside the scope of other statements like “if” statements. We also don’t rely on code that uses that multiline macro to follow specific coding conventions.

For more tips, and to arrange for personalized tutoring for yourself or your study group, check out Vancouver Computer Science Tutoring.