DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on • Edited 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.

Alternatively, you can use call_once() that is thread-safe. However, call_once() is a bit clunkier to use since it forces you to declare a flag explicitly and put the code into a separate function.

In C++, you can alternatively use std::call_once() that’s a bit better in that you can use a lambda rather than a separate function, but you still need to declare a flag explicitly.

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[A] 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[A] 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 (14)

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!

Thread Thread
 
pauljlucas profile image
Paul J. Lucas

I finally got around to reading about lobste.rs. It includes:

Self-promotion: It's great to have authors participate in the community, but not to exploit it as a write-only tool for product announcements or driving traffic to their work. As a rule of thumb, self-promo should be less than a quarter of one's stories and comments.

So I couldn’t post links to my own articles — but you could (if you wanted).

Thread Thread
 
gberthiaume profile image
G. Berthiaume

it would be my pleasure, that said, I haven't been invided.
I should work more in public.

Collapse
 
victor_oliari_df8ec7391d6 profile image
Victor Oliari

This is great content.

I had an issue reproducing RUN_ONCE macro though.

Code exert:

#define MYNAME2(A,B)         MYNAME2_HELPER(A,B)
#define MYNAME2_HELPER(A,B)  A ## B
#define MYUNIQUE_NAME(PREFIX)  MYNAME2(MYNAME2(PREFIX,_),__LINE__)

#define MYRUN_ONCE                     \
  static bool MYUNIQUE_NAME(run_once);    \
  if ( ( (MYUNIQUE_NAME(run_once)) ||     \
      (!(MYUNIQUE_NAME(run_once) = true)) ) ) std::cout << "lero"; else

int main(int argc, char* argv[]) {
    MYRUN_ONCE std::cout << "once\n";
    MYRUN_ONCE std::cout << "once\n";
}
Enter fullscreen mode Exit fullscreen mode

I get the following output

>once
>once
Enter fullscreen mode Exit fullscreen mode

In debug mode, I could see that each MYRUN_ONCE creates a different variable, namely:
run_once_42
run_once_43
.

I was expecting that the same variable would be created and thus MYRUN_ONCE would run only once.

In case it matters, I used a MSVC compiler. Am I getting something wrong here?

Collapse
 
pauljlucas profile image
Paul J. Lucas • Edited

You're misunderstanding what RUN_ONCE is supposed to do. It's working as intended. The idea is that it runs whatever code you give it once for each RUN_ONCE. Neither the macro nor compiler care what that code is, so if you just so happen to give two distinct calls to RUN_ONCE the same code, it assumes you know what you're doing and runs each bit of code once so it will look like it ran the code twice, but it actually didn't.

It's probably better to illustrate using:

void f() {
  RUN_ONCE std::cout << "one\n";
  RUN_ONCE std::cout << "two\n";
}

int main() {
  f();
  f();
}
Enter fullscreen mode Exit fullscreen mode

The code will correctly print one and two once each.

As of C11, you can just use call_once, but it requires an explicit flag and a separate function for the actual code. My macro just bundles the flag inside itself to be simpler and also allows an arbitrary block of code.

Collapse
 
victor_oliari_df8ec7391d6 profile image
Victor Oliari

Thank you for the fast and precise reply.

Now the flag intention is clearer to me.

I have to admit, this content is great, and I'm also learning new things. Thank you for the support.

Collapse
 
notgiven profile image
Notgranted • Edited

Is STRLITLEN() designed to work for char s[10] = "hello"; STRLITLEN(s) as well as STRLITLEN("hello")? If it is supposed to only allow the latter, one could do:

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

As consecutive string literals are concatenated in C, the empty string literal can provide rudimentary type-checking. Surely it can be broken, and more constructs can be added to make it more robust, but the added robustness may not be worth the added complexity.

Collapse
 
pauljlucas profile image
Paul J. Lucas • Edited

It's supposed to work for either. Given:

char const s[] = "hello";   // You don't need the size explicitly.
size_t len = STRLITLEN(s);
Enter fullscreen mode Exit fullscreen mode

it works just fine because the compiler knows the size. Given:

extern char s[];
size_t len = STRLITLEN(s);  // error
Enter fullscreen mode Exit fullscreen mode

it will fail because the compiler can’t know the size. The use of sizeof in the implementation already correctly handles this.

The valid but wrong case this is meant to catch is passing a pointer to it:

char const *const s = "hello";
size_t len = STRLITLEN(s);  // error
Enter fullscreen mode Exit fullscreen mode

Ordinarily, this would compile yet be wrong because sizeof would return the size of the pointer — but that’s why ARRAY_SIZE ensures its argument is an array and not a pointer.

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.