
C preprocessor tips and tricks - ingve
https://www.iar.com/support/resources/articles/advanced-preprocessor-tips-and-tricks/
======
catwell
A preprocessor "trick" not mentioned in the article is the use of GNU
statement expressions.

The article describes the do {} while(0) trick to make macros look like
statements. They can then basically be used like inline void function calls.
But what if you want to write a macro to replace an inline function that
returns a result?

Statement expressions [1] solve that problem. Now you can finally do this:

    
    
        int _foo(int x) { return 2 * x; }
    
        #ifdef __DEBUG__
        #   define foo(x) \({                     \
                int r = _foo(x);                  \
                printf(                           \
                    "foo returned: %d (%s:%d)\n", \
                    r, __FILE__, __LINE__         \
                );                                \
                r;                                \
            })
        #else
        #   define foo _foo
        #endif
    

They are supported by most popular compilers which are not made by Microsoft
(including GCC and clang).

[1] [https://gcc.gnu.org/onlinedocs/gcc/Statement-
Exprs.html](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html)

~~~
PeCaN
The canonical use of which being safe min/max:

    
    
        #define min(a, b) ({ \
          __typeof__(a) _a = (a); \
          __typeof__(b) _b = (b); \
          _a > _b ? _b : _a; \
        })
    
        #define max(a, b) ({ \
          __typeof__(a) _a = (a); \
          __typeof__(b) _b = (b); \
          _b > _a ? _b : _a; \
        })
    

which avoid evaluating their arguments twice.

------
tux3
It's a good introduction to the preprocessor, but I would dispute that it's
advanced tricks. The content is definitely interesting, but also very standard
practice.

There are many wonderful and horrible things that can be done with macros, and
some people get very creative, sometimes to a fault. It's always a special
feeling to see macros taking as argument other macros, all of that spreads
through a deep hierarchy over several files!

~~~
vmorgulis
> It's a good introduction to the preprocessor, but I would dispute that it's
> advanced tricks.

Yes. The X macro is missing for example.

[https://en.wikipedia.org/wiki/X_Macro](https://en.wikipedia.org/wiki/X_Macro)

I would also add #error, #line... and command line options to get the
preprocessed output.

------
emcrazyone
I follow the advise of Scott Meyers to prefer the compiler over the pre-
processor. Pre-processor code can inject bugs that which can't be easily
traced with debugging tools.

The pre-processor is pure evil and should be avoided when ever possible.

~~~
PeCaN
If you want to debug preprocessor-generated code, -gdwarf-4 -g3 usually does
the trick with GCC and Clang. If it doesn't, you can just gcc -E it and run it
through clang-format or something, and compile and debug the expanded version.
It's really not a big deal. You can have a Makefile rule to do this
automatically, for example
[https://github.com/alpha123/yu/blob/master/Makefile#L70-L103](https://github.com/alpha123/yu/blob/master/Makefile#L70-L103)

Saying the C preprocessor can "inject bugs" and make code that "can't be
easily traced" is deceptive and wrong. It is, for all intents and purposes, a
solved problem. That Makefile rule could be 3x shorter if I didn't go for some
extra niceties like not expanding system headers. (As usual, 20% of the result
takes 80% of the work). The preprocessor doesn't "inject bugs" anyway, unless
you write very careless macros. Modern C, with things like __typeof, _Generic,
and GCC/Clang's statement expressions means your macros can be totally safe
against overwriting variables, double-evaluation, type errors, and more. You
can, of course, still do silly things, but C isn't about protecting the
programmer anyway, and that's totally fine if you're prepared for it.

Perhaps in C++ the preprocessor is ‘pure evil’, but in C it's a very useful
part of the language.

~~~
emcrazyone
I work in the embedded space and use a compiler from Green Hills.

The preprocessor in either C or C++ is evil for the simple matter you can't
debug it when the thing you're debugging is in production, symbols are
stripped, and the disassemble resembles nothing you can marry up to actual C
or C++ code. In simple cases, maybe, but in not so simple cases it's just
evil.

A modern compiler will optimized out the global consts anyways too...

~~~
kllrnohj
If you have a modern compiler then it supports outputting the pre-processor
result.

If you have a modern toolchain stack unwinding is already friendly to macro
expansion.

A production build does not mean you don't have symbols, it just means you
don't have symbols in the executable. Stash your build symbols somewhere so
you can symbolize the stacks offline. Macros don't change this. They are
consistent output for the same input.

Macros are not evil. Abusable, yes, but they also solve various sets of
problems just _better_ than any other solution.

~~~
andrepd
Can you give an example of a problem which the preprocessor solves better than
modern C++?

~~~
ursus_bonum
I use preprocessor macros for what I guess I'll call "hyper-local extraction"
in an attempt to be as DRY as possible. I don't know how modern C++ would
handle this better. It looks a bit like this:

    
    
        #define a_ frob(-1,0,0); grob("a"); blah(...);
        #define b_ frob(1,0,0); grob("b"); blah(...);
        #define c_ frob(0,-1,0); grob("c"); blah(...);
        #define d_ frob(0,-1,1); grob("d"); blah(...);
        #define e_ frob(0,0,-1); grob("e"); blah(...);
        #define f_ frob(0,1,1); grob("f"); blah(...);
        
        if (normal_order) {
          a_ b_ c_ d_ e_ f_;
          c_ b_ a_ f_ e_ d_;
        } else if (special_order_1) {
          b_ c_ d_ e_ a_;
          d_ e_ c_ b_ a_ f_;
        } else if (...) {
          // ... and so on ...
        }
        
        #undef a_
        #undef b_
        #undef c_
        #undef d_
        #undef e_
        #undef f_

~~~
lmitchell
Modern C++ could use lambdas:

    
    
        auto a = [] {frob(-1,0,0); grob("a"); blah(...);};
        auto b = [] {frob(1,0,0); grob("b"); blah(...);};
        auto c = [] {frob(0,-1,0); grob("c"); blah(...);};
        ...
    
        if (normal_order) {
          a(); b(); c(); ...
        } else if (...) {
          b(); c(); a(); ...
        } ...

~~~
ursus_bonum
I guess that's pretty good.

------
greggman
Some people hate the preprocessor. I love it. I miss it in every language that
doesn't have one. I'd much rather be able to write

    
    
        statement
        statement
        statement
        #if DEBUG_FEATURE_X
            statement
            statement
            statement
        #endif
        statement
        statement
        statement
    

than

    
    
        statement
        statement
        statement
        // remember to comment this code out
        statement
        statement
        statement
        // --- end of debugging code
        statement
        statement
        statement
    

nor do I want a runtime check

Of course ultimately I just wish C/C++ and every other language let you use
the entire language at compile time like lisp (and a few others).

As for more advanced macro foo macro lists are my favorite

[http://games.greggman.com/game/keeping_lists_in_sync_in_c__/](http://games.greggman.com/game/keeping_lists_in_sync_in_c__/)

I know some people don't like them. Typically those people would solve the
issue by using some other language, python, to generate C/C++. I'd prefer not
to have yet another dependency.

~~~
dilap
preprocessor tricks are cute, but there's definitely something to be said for
staying completely w/in the language -- I've really come to appreciate this
using Go, where I get incredible auto-formatting, rename refactor, and source
introspection tools that would not be possible if a preprocessor were
standard-issue.

(Of course, Java people have been saying something similar for a long time,
but I never could get into it, 'cuz of a bunch of other stuff, like the IDE-
centric culture & the language itself)

~~~
generic_user
> but there's definitely something to be said for staying completely w/in the
> language

The C Pre Processor is defined in the ISO C standard. In order for any
implementation to call it self a normative conforming C implementation I has
to include a pre processor to spec. The Pre Processor is part of the ISO C
language.

> auto-formatting, rename refactor, source introspection tools

All of those things are had quite easily with C. Either as IDE/Editor features
or with separate tools such as Clang Complete, Valgrind and many others.

~~~
dilap
spent a long time w/ c and c++, never found tools anywhere near as good as
what i've got now for go.

maybe/probably not impossible, but a lot harder in practice. i have no
personal experience, but people that seem to know what they're talking about
say a big reason is the preprocessor makes life a lot harder. (and other stuff
too, of course, like not having build semantics built into the language, and,
for c++, just insane complexity in general.)

obviously the preprocessor is well-defined and standardized, but it still:
operates on a totally different semantic level. it really is like two entirely
different languages pasted together.

~~~
generic_user
Would you mind listing a few tools you use with go?

I do not have much exposure to go but from what I have seen there is a nice
set of introspection libraries. are you talking about tools that use these?

A final note, real time semantic checking of multiple expansion macros has
been solved for quite a few years now and is in most IDE's and even in Vim and
Emacs. Semantic analysis is done in the comelier, LLVM/Clang lib has given
rise to a substantial set of tools that are now quite widely used and solve
most of the problems.

And I believe C++17 with concepts is also going to have a dramatic effect on
complexity and readability of template error messages.

~~~
dilap
these days my only c use is via obj-c in xcode, so i may well be unaware of
the state of the art for straight c or straight c++ / other tools. xcode's
refactorings and jump-to-defn (presumably powered by clang?) are still very
flaky for me.

for go, i use:

goimports -- which is gofmt (which is awesome) + auto-adding/removing imports
(just a heuristic so not always right, but usually right and quite handy)

godef -- for jumping to definitions

gorename -- for rename refactorings. i have _not once_ gotten this to break
code.

oracle -- for showing other things about code, like 'which types implement
this interface?'. as far as i can tell, it hasn't lied to me yet.

i use emacs as my editor, which integrates easily w/ all of the above tools.

i'm not sure what libraries power the tools above, but i think it's true that
packages like go/types do the heavy lifting and make them relatively easy to
write.

------
_kst_
> #ifdefs don't protect you from misspelled words, #ifs do.

That's not (typically) correct. If an undefined identifier is used in a
preprocessor expression, it expands (contracts?) to a constant 0. gcc does not
warn about using undefined identifiers by default, and if there's an option to
make it do so I'm not familiar with it. ( _EDIT: I am now, it 's "-Wundef".
Thanks, sigjuice._) For example, this program:

    
    
        #include <stdio.h>
        
        #define MISPELLED
        
        int main(void) {
        #if MISPELED
            puts("MISPELED is true");
        #else
            puts("MISPELED is false");
        #endif
        
        #ifdef MISPELED
            puts("MISPELED is defined");
        #else
            puts("MISPELED is not defined");
        #endif
        }
    

produces no compile-time diagnostics and prints this output:

    
    
        MISPELED is false
        MISPELED is not defined
    

If you have a C compiler that warns about this, that's great -- but you're
likely to get spurious warnings for some code.

~~~
sigjuice
gcc has -Wundef (Warn if an undefined identifier is evaluated in an #if
directive.)

~~~
PeCaN
Which is a enabled by default with -Wall.

I'm a fan of compiling with -Wall -Wextra and manually disabling any
irrelevant warnings (which is rare, and preferable to do on a per-file basis
in the Makefile).

------
fizixer
I'm desperately looking for a C preprocessor that dumps the AST (preferable in
JSON format) instead of expanding the macros. And it treats the C/C++ code as
just data.

I plan to take the rest of the code and send it through clang to get C/C++ AST
dump. Currently I'm getting clang's AST dump which "includes" all the headers
and the size of the AST just blows up (e.g., 300k lines of AST for a 4k line
C++ file that has about 10 include files).

~~~
daurnimator
Try CParser:
[https://github.com/MatzeB/cparser](https://github.com/MatzeB/cparser)

------
dicroce
My favorite "feature" of the pre processor is when I include Windows.h and
then get a super strange compile error in my code because a macro Windows
defines (with a super generic name) matches part of a name in my code (which
is in a namespace btw) and expands into some unholy thing that is invisible at
the source level... Fun times.

~~~
stagger87
#undef

------
m6w6
The rationale for #if vs #ifdef was interesting, though.

------
yoklov
Deferred expansion is a common (well, not that common) trick this misses that
I would have included. Generally the C preprocessor is pretty terrible though,
you should seriously consider generating code with an external tool before
doing anything nontrivial with it.

Regardless, here's a link that seems to cover this and other somewhat more
obscure techniques: [https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-
tricks,...](https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-
tricks,-tips,-and-idioms)

------
maxxxxx
Reading this makes me a little sad. I know the preprocessor can be misused but
I really miss it in C#. A lot of highly repetitive code could be written much
more elegant with a few well written macros.

~~~
PeCaN
There's always M4, which will preprocess just about anything you throw at it.

The syntax starts off pretty godawful, but eventually you learn to love it. C#
and Java are much nicer with a little M4.

Everybody loves to hate on text-substitution macros (and hey, I write parsers
and such, I'm a Smug Lisp Weenie, and generally I know how easy it is to break
something and how much better real macros are), but sometimes they actually
are the right tool for the job. Just because they're sometimes very much the
wrong tool doesn't mean they should be kept out of your toolbox. Until all
languages are either Lisp-ish or Forth-ish, I will maintain that there is a
place for CPP/M4/etc. :-)

M4 is seriously awesome, just remember: with great text substitution comes
great attention to detail. And heed the warning from the manual[1]:

> Some people find m4 to be fairly addictive. They first use m4 for simple
> problems, then take bigger and bigger challenges, learning how to write
> complex sets of m4 macros along the way. Once really addicted, users pursue
> writing of sophisticated m4 applications even to solve simple problems,
> devoting more time debugging their m4 scripts than doing real work. Beware
> that m4 may be dangerous for the health of compulsive programmers.

1\.
[https://www.gnu.org/software/m4/manual/m4.html](https://www.gnu.org/software/m4/manual/m4.html)

~~~
speps
For C# based environments, you should use T4[1] actually. It comes with
Mono[2] as well as the normal CLR. It has an API and a command-line tool.

[1]: [https://msdn.microsoft.com/en-
us/library/bb126445.aspx](https://msdn.microsoft.com/en-
us/library/bb126445.aspx) [2]:
[https://github.com/mono/monodevelop/tree/master/main/src/add...](https://github.com/mono/monodevelop/tree/master/main/src/addins/TextTemplating/TextTransform)

------
ape4
Best to avoid macros if possible. use inline functions. Use global variables
instead of MAX_BUF, etc

~~~
_kst_
The name of a variable, global or otherwise, is not a constant expression, so
there are contexts in which it can't be used.

~~~
xiaq
Enums can be used for that purpose.

~~~
_kst_
True, but only for type int.

    
    
        enum { MAX = 1000 };

~~~
J_Darnley
Why is this preferable over a preprocessor define? Is it because it can't be
redefined easily? A max or count is certainly useful when there are other enum
values but I don't see the huge advantage it has here.

~~~
_kst_
Enumeration constants have scope.

------
kbart
That's the very basics of C preprocessor and the last part (#ifdef vs #ifs)
didn't convince me at all, it's just a matter of programming style and use
case.

------
adenner
Just because you can doesn't mean that you should

------
icholy
Protip #1: don't use them.

~~~
uxcn
In C++, probably never. In C, they can be necessary for doing type orthogonal
things (like intrusive data structures). Type generic expressions in C11 can
be a bit of a safer alternative for some stuff, but you generally need to give
explicit implementations for each type.

For example,

    
    
        #define sqrt(x) _Generic((x), long double: sqrtl, float: sqrtf, default: sqrt)(x)

~~~
blt
Even in C++, they are good when you want to encapsulate a statement that may
return from the enclosing function. Every time I write a parser or interpreter
in C++, I end up writing a macro like

    
    
        #define EXPECT_TYPE(node, Type) if ((node)->type != Type) { return FAIL; }
    

and using it extensively.

~~~
uxcn
Also things like generating code at compile time, but a lot of people could
argue it both ways.

