DEV Community

Handy C/C++ Preprocessor Macros

Paul J. Lucas on January 02, 2024

Introduction Programming in either C or C++ invariably requires using preprocessor macros at some point. Here’s a collection of macros I...
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.