Tips And Tricks For The C Pre-processor

C Pre-processor

The C pre-processor can help you write more concise, easy to follow code. It can also let you create a tangled ball of macros and #defines. [s1axter] wrote up a guide on how to use the pre-processor and keep your sanity.

We’ve seen some neat hacks with the C pre-processor, such as a full adder implementation, but this focuses on more practical usages. First, [s1axter] explains what the pre-processor does with your code by writing simple macros. Next up is arguments, and usage of ‘##’ directive for metaprogramming. Finally, we get a good explanation of why you need to worry about scope when using macros, and how to safe code by using ‘do {} while()’ statements.

If you’re into embedded programming, this guide will help you understand some of the more complex pre-processor techniques out there. It’s helpful for making your code clearer, and abstracting away hardware dependencies in a few lines of code.

54 thoughts on “Tips And Tricks For The C Pre-processor

  1. I’ll be that old fart: the C preprocessor should be avoided as much as possible. It is legacy; the value it provides is relatively weak as far as generating code goes. For common uses, there are typesafe and namespace-friendly mechanisms to provide the same behavior with const, enum, and/or inline functions.

    In this case, you dont need to wrap with a zany do {…}while(0). You can simply explicitly declare scope with {…}.

    One of the few reasons is to use a macro is something like an ASSERT, where the macro can expand to include the file, line, function, expression, etc.

    Save yourself the bugs, dont use macros!

      1. I don’t agree. I work in embedded C as well and I find that using #defines inside of the .c file (i.e., not in the header) for very simple macros (e.g., MIN/MAX) or constant values works great.

          1. Here is the ADC channels on my circuit where 0-7 are hooked up to external hardware and 0x0e, 0x0f are internal to the chip.

            enum ADC_HW_Channels { VBAT1, IBAT1, IBAT0, VBAT0, THERM1, THERM0, VIN, BUTTONS_IN, VBG=0x0e, VGND=0x0f };

            It is very compact and also allow you to explicitly assign values. I also used enum as different states to my state machine.

      2. You can write clear code using the preprocessor, and you can write the most impenetrable crap without it. Personally I use it (embedded C here as well), but I’m quite careful with how it’s applied.

        1. I agree, I mainly use it for constants or to remove magic numbers and bitmath that makes more sense with a name than a load of logic and bit shift operations, but doesn’t need the overhead of calling a function.

    1. I’m glad you always get to work with a modern, working compiler that can do things like inline cleanly.

      Some of us embedded guys are stuck with half-working pieces of crap where macros are the only way to inline code.

      1. No compiler support for alternatives is a perfectly good reason to use preprocessor macros; it’s also a good reason to find another compiler :-)

        If you have a compiler with good C++ support, then inline template functions are also interesting; you could ‘templatize’ the above to support generic UARTs and, again with a good compiler, suffer no extra code.

        It’s easy enough to write a simple case and look at the object code generated.

        Here’s a good intro on C++ ‘inline’ (not specific to embedded stuff)
        http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.1 (sorry, don’t know how to drive links here…)

    2. Just because it’s an antiquated concept doesn’t mean it’s functions are entirely replaced. In my shader system I have a file which uses the C pre-processor to construct the material based on parameters defined in the shader it’s used in. Used a similar thing when I made a small OS as a hobby venture, it’s a wonderful system when your dealing with static branching for each of a components uses.

  2. If the C compiler honors the inline keyword, I’d use that over these macros … but if it doesn’t, and I’m even remotely concerned about performance, these macros are less ugly than the alternative.

    Also: do {…} while 0 has a well-documented reason why you HAVE to do it that way (no semicolon) and can’t just use {}. JFGI.

    That said, given the wrapped syntax, this looks like PIC stuff; be careful because at least on the lesser members of their product line, you can’t modify RXIF directly.

    1. That reason being (mostly) that if people use an expandable macro after an if statement with no brackets bad things will happen. If the macro has its own semicolon and they add the macro with an extra semicolon that’ll cause more trouble.

    2. The “inline” keyword does have it’s issues. GCC changed the inline keyword to be a hint instead of a forced action and gcc will inline and not-inline at will. Linux has a nice rant about it somewhere online.

      (I still usually do inline functions instead of macros. But sometimes macros can do things that functions cannot)

      1. Are you saying you tried C++ templates and got back? :-)

        Well, let’s compare the options: with macros, I need to end each end of line of my “inline function” with backslash character and make sure there aren’t any blanks (invisible, duh) on the line past them. Trivial, but burns my time unnecessarily. There is no type checking, which may look like a not big deal, since you generally define type as macro, but it won’t warn you if you use an identificator for type which is already assigned as something else.

        Second option, inline functions: much better, no pesky backslashes, but you have to fix the types. OK, you can repeat the trick of defining types as macros, but it suffers the same drawback as previous option.

        Third option, templates … hmm, I don’t like the syntax with “”, it stands out like a sore thumb, but watchya gonna do?

        1. I tried C++ for embedded software. At first I thought it was great, and then I started to look at all the code bloat that was generated under the hood. At first, I tried to remedy that with clever C++ tricks but I started to notice how much effort it was compared to just writing everything in C, so I went back.

          Most of my C macros are just simple one-liners that don’t require backslashes. And the occasional macro that uses backslashes couldn’t be converted to C++ anyway.

        2. For backslash-free macros (Boost Method):
          Place your macro function in its own header file, then define your macro like so:

          #define MYMACRO() “mymacro.h”

          Then call like this:
          #include MYMACRO()

          Arguments can be handled as follows:
          MYMACRO_ARGUMENTS(A1, A2, A3)
          #include MYMACRO()

          Obviously this multiline approach may annoy some people. However, with “Large” macros, the benefit of macro readability is worth the visual warts….

    1. Except that templates make for horrible compiler errors/warnings, which makes them tricky to use (next to templates solving a totally different issues then inline functions and macros)

      c:\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.7.1\include\c++\ext\new_allocator.h|83|error: ‘const _Tp* __gnu_cxx::new_allocator::address(__gnu_cxx::new_allocator::const_reference) const [with _Tp = const CSG::Vector; __gnu_cxx::new_allocator::const_pointer = const CSG::Vector*; __gnu_cxx::new_allocator::const_reference = const CSG::Vector&]’ cannot be overloaded|
      c:\codeblocks\mingw\bin\..\lib\gcc\mingw32\4.7.1\include\c++\ext\new_allocator.h|79|error: with ‘_Tp* __gnu_cxx::new_allocator::address(__gnu_cxx::new_allocator::reference) const [with _Tp = const CSG::Vector; __gnu_cxx::new_allocator::pointer = const CSG::Vector*; __gnu_cxx::new_allocator::reference = const CSG::Vector&]’|

      (if you try to make a std::vector)

      1. You can certainly choose to go the route of failure like you did. Every language has features that will cause head-scratching and obscure error messages IF YOU CHOOSE TO GO THAT WAY.

        Or you can treat templates as smarter macros and start simple and achieve smaller code size and reduced complexity.

        You really need to surf over to Dr.Dobbs magazine and see how to actually use templates effectively. They have a series of articles on how to FFTs using C++ templates, their solution is faster than any known C implementation.

  3. C++ templates are nice except for the fact that they require C++, which is ugly. So the advantage of C++ templates are completely overwhelmed by the rubbish that is C++.

    On the topic of not using C preprocessor, for embedded I consider it the way Linus considers C++ for systems programming: if you can’t understand my C preprocessor code, I don’t want you on the project anyway. Sorry, but it’s true. Good programs are good because they are written by good programmers, simple as that.

    1. And good programmers don’t waste their time writing “efficient” code that a compiler will do for you and hide all of the gory details. In many cases C++ can be much more efficient especially if you’re trying to write generic, reusable code.

      1. Only if a programmer doesn’t know what he’s doing, is C++ more efficient. And for hard real time embedded work, you don’t want to hide the gory details. You want them in plain sight, so you can verify that the program is correct.

          1. Not at all. C is about optimal. It combines the convenience of a higher language, while still giving the (competent) programmer complete control and insight. With C++ too much happens under the hood, which is especially dangerous when dealing with code from somebody else. In C++, I have no idea what a = b + c means.

      2. Slyclops: I had a pretty lucrative time as an embedded consultant, mostly thanks to people who care more about maintainability than actually getting the job done in the first place. So, company X would develop their FW using C++ best practices, and they would fail to meet benchmarks, and then they would hire me to undo it all and implement it in close-to-the-metal C.

        1. Well best practices should have been in quotas there. If they really did, then they would not fail to meet the benchmarks. C++ is not only C with classes and there is no reason other than lack of knowledge that the end product be slower/bigger/etc compared to C.
          I suggest those companies get their best practices right, i.e. take an embedded C++ training from Scott Meyers, for instance.

          Of course C++ is not only the language specification but also the compiler and the standard library. There are now much more compact libraries like newlib and newlib-nano for use in embedded systems.

    2. You DO NOT have to use C++ features, nobody is forcing you. You can write very efficient code in C++. You ARE NOT REQUIRED to write obfuscated C++ code. C++ is a SUPERSET of C, you are free to pick and choose which C++ features you use.

      Yes it takes RESTRAINT to keep from going overboard and writing code where a = b+c is obfuscated. But it CAN be done. And you CAN enforce such restraint on your fellow developers.

  4. I don’t know C and don’t really have any intent to learn it, but I love reading about it. I’m especially a big fan of insane art-coding that uses pre-processor to implement the whole program.

  5. Yup…. all the above posters write code. One can tell because to understand any more than 25% of the total postings requires excessive scrolling up and down in desperation trying to figure out what the heck is going on. Final confirmation comes from following the provided links for more information and this is as likely to confuse things more as it is to clear anything up. The quality of the programmer(s) (is|are) nearly proportional to the strength of headache you get making these almost futile attempts to understand what is going on, and your conclusion is pretty much confirmed if at the same time you feel like you’ve been watching Laurel and Hardy doing their “Who’s on first” routine!

    It all makes me very happy to know a few programmers, (always great folk), and have just scant enough of my own coding skills to barely make my shiny new scratchbuilt widget board pound nails in like it should.

    I understand aspirin a lot better since I met programmers!

      1. Oh, I understand fully. Most of my coding was in machine code and assembly back in the late 70’s and it was just a required part of the electronics work cause ya haveta make the darn thing work once you designed and built it. What you all got now with C is a beautiful language but flush with so many features, conveniences, and capabilities that only a few can understand it all and make proper use of it, (as per testimony given in the thread) and it will drive the rest to baldness. I feel for ya. Only coding I do now is for personal entertainment and is FUN!

        Management? Darn, no! Glass ceiling effect. Disqualified due to too much ability to produce actual usable $$$ work product without bs. Only perk I got was they assigned me a secretary so my time stopped being lost to paperwork, policies, and procedures.

        F is a very smart person!

  6. I don’t understand the big argument over assembly vs c vs c++.
    Each language has good and bad features, and areas of application (Which do overlap).
    I routinely use all three in my embedded projects, which lets me write better code, faster.

    The same is said for using Macros vs Inline Functions vs Templates. They all have benefits, so I use them when they are needed :).

    (I also use the BOOST Preprocessor Library, which helps with macro wizardry. Its also “documented”, and could be considered a “de-facto” standard)

      1. Oh… it’s not superstition…. you’re seeing the effect of learning about anode-cathode potential by personal experience! The guy that influenced me most while learning was called “Fuzz” and he had an accuracy to within 10v over a 1500v range between two fingers. Had the shakes just about as much as some of the better programmers I know today but they lack the crew-cut.

  7. I noticed one problem in this site – he doesn’t parenthesize macro arguments.

    #define foo(x) ((x)*3)

    Why? Because of things like this, which don’t work properly:

    foo(y+2)

    Without the parens, that expans to:
    (y+2*3), which clearly is not what you want.

    With parens, it expands to:

    ((y+2)*3), which is probably what you want.

    Other suggestions: macros should only use their arguments once, and arguments to macros should never contain side effects (pre or post increment), for example:

    #define foo(x) ((x)*(x))

    What happens when you call:

    foo(x++)

    LIkewise, make your macro names all caps so you know they are macros.

    #define FOO(x) (x*3)

Leave a Reply to TrevorCancel reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.