DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on • Updated on

Handy C/C++ Preprocessor Macros

Introduction

Programming in either C or C++ invariably requires using preprocessor macros at some point. Here’s a collection of macros I find particularly handy in most any program. These macros work in either C or C++.

NAME2

This macro concatenates two identifiers together:

#define NAME2(A,B)         NAME2_HELPER(A,B)
#define NAME2_HELPER(A,B)  A ## B
Enter fullscreen mode Exit fullscreen mode

For example, NAME2(foo,bar) will expand into foobar.

It actually will concatenate any two tokens together, but concatenation is invariably used for identifiers.

The reason for NAME2_HELPER has been explained previously in detail.

Why is this handy? Wait and see.

UNIQUE_NAME

This macro forms a “unique” name:

#define UNIQUE_NAME(PREFIX)  NAME2(NAME2(PREFIX,_),__LINE__)
Enter fullscreen mode Exit fullscreen mode

Well, unique enough for most cases. Specifically, it forms a unique name only for the line it’s on, for example, UNIQUE_NAME(var) would expand into something like var_42.

Why is this handy? As shown in the macros below, having a unique name allows you to use the same macro multiple times in the same scope or nested scopes and avoid shadows warnings.

ASSERT_RUN_ONCE

This macro will assert if it’s executed more than once:

#ifndef NDEBUG
#define ASSERT_RUN_ONCE()            \
  do {                               \
    static bool UNIQUE_NAME(called); \
    assert( !UNIQUE_NAME(called) );  \
    UNIQUE_NAME(called) = true;      \
  } while (0)
#else
#define ASSERT_RUN_ONCE()  (void)0
#endif /* NDEBUG */
Enter fullscreen mode Exit fullscreen mode

It’s for use in initialization type functions to guarantee they’re called at most once, e.g.:

void conf_init( void ) {
  ASSERT_RUN_ONCE();
  // ...
}
Enter fullscreen mode Exit fullscreen mode

It’s defined only if NDEBUG (the macro used with assert) is not defined since it’ll work only when compiling with assertions enabled (the default).

This implementation isn’t thread-safe. However, it’s fine if a program doesn’t use more than one thread. If a program does use more than one thread, a thread-safe version is possible and not that much harder, but it’s left as an exercise for the reader.

RUN_ONCE

This macro will run a statement exactly once:

#define RUN_ONCE                     \
  static bool UNIQUE_NAME(run_once); \
  if ( (UNIQUE_NAME(run_once) ||     \
      !(UNIQUE_NAME(run_once) = true)) ) ; else

int main( int argc, char const *argv[] ) {
  RUN_ONCE conf_init();
  // ...
Enter fullscreen mode Exit fullscreen mode

Usually, it’s a best-practice to enclose multiple statements between a do ... while loop; however, in this case you can’t use one and have the else work. Despite this, it’ll work in most cases.

Similarly, this implementation isn’t thread-safe either. Again, a thread-safe version is left as an exercise for the reader.

ARRAY_SIZE

This macro will return the number of elements in a statically allocated array:

#define ARRAY_SIZE(A)  (sizeof(A) / sizeof(0[A]))
Enter fullscreen mode Exit fullscreen mode

Yes, the syntax of 0[ARRAY] is legal. It’s a consequence of the quirky interplay between arrays and pointers in C. Briefly, the a[i] syntax to access the ith element of an array a is just syntactic sugar for *(a+i). Since addition is commutative, *(a+i) can be alternatively written as *(i+a); that in turn can be written as i[a]. In C, this has no practical use.

So why use it here? In C++, however, using 0[ARRAY] will cause trying to use ARRAY_SIZE on an object of a class for which operator[] has been overloaded to cause a compilation error, which is what you’d want.

While ARRAY_SIZE works fine, it can also be wrongly used on an “array” parameter:

void f( int a[] ) {  // really, int *a
  for ( size_i i = 0; i < ARRAY_SIZE(a); ++i ) // WRONG
    // ...
Enter fullscreen mode Exit fullscreen mode

As I’ve mentioned previously, array parameters simply don’t exist in C (or C++): the compiler rewrites such parameters as pointers.

Some compilers warn about this. For those that don’t, can ARRAY_SIZE be defined such that it’ll generate an error if it’s used on a pointer? Yes (mostly).

IS_ARRAY

The first thing needed is a way to determine whether the type of A is actually a statically allocated array (as opposed to a pointer). C++ has std::is_array, but what about C?

As of C23, you can use typeof along with _Generic:

#define IS_ARRAY(A)       \
  _Generic( &(A),         \
    typeof(*A) (*)[]: 1,  \
    default         : 0   \
  )
Enter fullscreen mode Exit fullscreen mode

This works because if A is actually an array:

  1. The &(A) yields “pointer to array of type T.”
  2. The A (inside typeof) “decays” into a pointer to its first element yielding “pointer to T,” i.e., T*.
  3. The *A dereferences T* yielding the element type T.
  4. Finally, T (*)[] yields “pointer to array of type T” which matches 1 above and _Generic returns 1 (true).

If A isn’t an array, e.g., a pointer, then none of the above works and _Generic matches the default case and returns 0 (false).

If you’re using a version of C prior to C23, both gcc and clang support typeof (or __typeof__) as an extension.

So far, so good; but how can IS_ARRAY be used with ARRAY_SIZE such that it’ll fail to compile when given a pointer?

STATIC_ASSERT_EXPR

C has static_assert, but it’s more like a statement. What’s needed is a way to use it in an expression. The trick is to realize that static_assert can be used pretty much anywhere, including inside a struct declaration that’s an argument to sizeof() that makes the whole thing an expression:

#define STATIC_ASSERT_EXPR(EXPR,MSG) \
  (!!sizeof( struct { static_assert( (EXPR), MSG ); char c; } ))
Enter fullscreen mode Exit fullscreen mode

If EXPR is non-zero, sizeof() will return non-zero that !! will convert to 1; if EXPR is zero, then you’ll get a compile-time error that the assertion failed. (The char c is there just so the struct isn’t empty.)

ARRAY_SIZE 2.0

Given all that, we can now do:

#define ARRAY_SIZE(A) (     \
  sizeof(A) / sizeof(0[A])  \
  * STATIC_ASSERT_EXPR( IS_ARRAY(A), #A " must be an array" ))
Enter fullscreen mode Exit fullscreen mode

If A is really an array, the STATIC_ASSERT_EXPR will be 1 and multiplying by 1 is innocuous. (The compiler will optimize the multiplication away.)

FOREACH_ARRAY_ELEMENT

Now that we have ARRAY_SIZE that will work only on arrays, we can use it to define a convenience macro:

#define FOREACH_ARRAY_ELEMENT(TYPE,VAR,A) \
  for ( TYPE const *VAR = (A); VAR < (A) + ARRAY_SIZE(A); ++VAR )
Enter fullscreen mode Exit fullscreen mode

that reduces the boilerplate code to iterate over all elements of a statically allocated array.

STRLITLEN

Now that we have ARRAY_SIZE that will work only on arrays, we can use it to define:

#define STRLITLEN(S)  (ARRAY_SIZE(S) - 1)
Enter fullscreen mode Exit fullscreen mode

that gets the length of a C string literal (an array of char) at compile-time.

Conclusion

I hope you’ll agree that these macros are handy. Feel free to use them in your programs.

Further Reading

Here are other articles I’ve written that involve preprocessor macros:

Top comments (7)

Collapse
 
gberthiaume profile image
G. Berthiaume

I was not expecting to find such a high-quality low-level programming post on dev.to.
The STATIC_ASSERT_EXPR is brilliant.
Thanks for sharing.

Collapse
 
pauljlucas profile image
Paul J. Lucas • Edited

Thanks. I'm doing my part to raise the bar. The number of low-quality, poorly-written posts is eye-watering. FYI, it's no better on medium.

Collapse
 
gberthiaume profile image
G. Berthiaume • Edited

I agree. Maybe you could post links to your articles to lobste.rs. They would love your post over there.

In any case, I'm now following you. :)
Looking forward to reading your next C articles.

Thread Thread
 
pauljlucas profile image
Paul J. Lucas

I'd never heard of lobste.rs. I'll give it a look.

I'm currently spending time working on one of my open-source projects rather that writing. In the mean time, there's all my previous articles to read.

Thread Thread
 
gberthiaume profile image
G. Berthiaume

I'll be sure to check them out.
Have a great day!

Collapse
 
pgradot profile image
Pierre Gradot

I never realized (or I forgot, that's also quite possible) that LINE is the same for all the lines that a macro expands to :o

Collapse
 
pauljlucas profile image
Paul J. Lucas

A macro always expands to a single line. Remember: escaped newlines are eliminated prior to expansion.