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
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__)
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 */
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();
// ...
}
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();
// ...
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 theelse
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]))
Yes, the syntax of
0[A]
is legal. It’s a consequence of the quirky interplay between arrays and pointers in C. Briefly, thea[i]
syntax to access the ith element of an arraya
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 asi[a]
. In C, this has no practical use.So why use it here? In C++, however, using
0[A]
will cause trying to useARRAY_SIZE
on an object of aclass
for whichoperator[]
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
// ...
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 \
)
This works because if A
is actually an array:
- The
&(A)
yields “pointer to array of type T.” - The
A
(insidetypeof
) “decays” into a pointer to its first element yielding “pointer to T,” i.e.,T*
. - The
*A
dereferencesT*
yielding the element type T. - Finally,
T (*)[]
yields “pointer to array of type T” which matches 1 above and_Generic
returns1
(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
andclang
supporttypeof
(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; } ))
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" ))
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 )
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)
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)
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.
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.
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.
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.
I'll be sure to check them out.
Have a great day!
I finally got around to reading about
lobste.rs
. It includes:So I couldn’t post links to my own articles — but you could (if you wanted).
it would be my pleasure, that said, I haven't been invided.
I should work more in public.
This is great content.
I had an issue reproducing RUN_ONCE macro though.
Code exert:
I get the following output
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?
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 eachRUN_ONCE
. Neither the macro nor compiler care what that code is, so if you just so happen to give two distinct calls toRUN_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:
The code will correctly print
one
andtwo
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.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.
Is
STRLITLEN()
designed to work forchar s[10] = "hello"; STRLITLEN(s)
as well asSTRLITLEN("hello")
? If it is supposed to only allow the latter, one could do: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.
It's supposed to work for either. Given:
it works just fine because the compiler knows the size. Given:
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:
Ordinarily, this would compile yet be wrong because
sizeof
would return the size of the pointer — but that’s whyARRAY_SIZE
ensures its argument is an array and not a pointer.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
A macro always expands to a single line. Remember: escaped newlines are eliminated prior to expansion.