embedded software boot camp

Effective C Tip #9 – use #warning

Thursday, September 1st, 2011 by Nigel Jones

This is the ninth in a series of tips on writing effective C. Way back in 1999 I wrote an article for Embedded Systems Programming concerning the #error directive. If you aren’t particularly familiar with #error, then I suggest you read the article. While the #error directive has remained one of my most popular tools, I have become an equally big fan of #warning.

Before I delve into the uses of #warning, I must warn you (if you would pardon the pun) that #warning is a non standard directive. However it is supported by IAR, GCC, Microchip’s C18 compiler, Hi-Tech and probably a whole raft of other vendors. In other words, it’s pretty standard for a non-standard feature.

The use of #warning is simple enough:

#warning This is a warning

This will result in the compiler issuing a warning with the text ‘This is a warning’ printed to stderr. Please note that, just as for #error, there is *no* requirement that the text be in quotes. If you insist on putting quotes around the text, then they will be printed to stderr as well.

With the syntax out of the way, here’s some of the ways that I use #warning.

Protecting Incomplete Code

Very often when I’m coding, I like to get the big picture in place without worrying about the minutiae of the implementation details.  As a result I end up with functions or loop bodies that are incomplete. In these cases I simply add a #warning to alert me to the issue. For example

void foo(void)
{
#warning To be completed
}

Thus what happens now is that whenever I compile the module, I get a warning (i.e. reminder) that there is something important still to be done.

Commenting Out Code

I *never* comment code out anymore as part of the debugging process, as it is simply too easy to forget that the code has been removed. Instead I use this construct:

void foo(void)
{
# if 0
   /* Code to be temporarily removed is here */
#else
#warning Temporary debug construct. Fix me!
   /* Experimental code goes here */
#endif
}

In this case whenever I compile the module, I get a warning (i.e. reminder) that I have some experimental code in the image.

The Key Final Step

While the above are useful constructs, the real power of #warning comes if you configure your compiler to treat warnings as errors for the release build. If you do this and you have inadvertently left incomplete or debug code in the image, then your compilation will fail. In short, this technique will guarantee that you never release code that includes / excludes code that shouldn’t / should be there. That’s effective C.

Previous Tip

 

 

28 Responses to “Effective C Tip #9 – use #warning”

  1. Gauthier says:

    I’ve read the “not commenting out code for debugging purposes” before, in Michael Barr’s standard I think. I really don’t understand why, doesn’t everybody review a diff of the source before committing? A diff with 30 commented lines of code is much more visible than the two rows of “#if 0 / #endif”.
    Even in the editor, a chunk of lines colored as comments are far easier to spot than the two precompiler lines.

    • Gauthier says:

      (about “easier to spot in the editor”: that’s where your #warning construct makes a big difference, although only visible at compile time.)

    • Seems to me that the warning is the more important part – assuming that the compiler supports it.

      Having said that, I must say that the look-see method before a commit is a little unreliable. Not everyone always does the diff; I know that to be true because I myself don’t always do it – particularly if I’m the only one working on the code and/or the change I made is small.

      Although I’m occasionally “guilty” of commenting out for debugging, I believe Nigel has it right. My thought is, after reading this, to write a macro or three to use instead of the plain #if…#else…#endif and to incorporate in these not only a standard #warning but also an #error message based on the setting of NDEBUG (or whatever else I might use to distinguish between debug and release). Haven’t got time to concoct those macros right now, though!

      • Using #warning is most effective when you compile your release code with warnings as erros. So you can develop software with commented out code, but when it is going to be released outside compilator will force you too clean all your #warnings.

    • Lundin says:

      To comment out code is banned by MISRA-C, that’s probably where you (and/or Barr) picked it up.

      • Richard Hendricks says:

        I prefer to comment out code with

        /////RAH (explanation of what is being done)

        when I do debug. This way once I have finally resolved what is wrong, I can search for my specific comments to back out any unrelated changes etc. After than, I usually pull a fresh copy from the SVN, and re-introduce my minimal bug fix.

        Although, this method with #warning does seem interesting. My problem is that in the code bases that I touch, device driver development in Windows and Linux, it’s common to get hundreds to thousands of warnings. For this to be really useful, post-compile a search would need to be done against the output. Maybe a command could be added to the makefile for grepping these specific warnings? It seems like an additional step that just wouldn’t happen consistently enough.

        • Nigel Jones says:

          Wow! Whatever happened to ‘warning free’ code? Perhaps this explains why device drivers for Windows / Linux cause so many problems?

          • Jim Sheahan says:

            Yeah, I know its been a while that this post was, well.. posted, but I entirely agree with Nigel; definition of warning – something that serves to warn, give notice, or “CAUTION”

      • Gauthier says:

        Do you know the rationale behind this advisory rule? My best guess is that code that is present in the source file but does not execute is confusing (was it meant to execute? is the commenting a mistake?). But in that case, #if 0 has exactly the same problem. #if 0 is even more likely to be interpreted as a “commented out by mistake” than a block comment (noted that this is solved with Nigel’s use of #warning).

    • Sibin says:

      A good editor (like Vim) would show the code under #if 0 as comments too.

  2. Fernando says:

    Really interesting stuff, as usually.

    Regards,
    Fernando

  3. david collier says:

    Very interesting….

    what I’d really like is an understanding of the way(s) to do “assert” in C…. and whether any of them work across all compiler.

  4. Allen Moore says:

    One of the (older) compilers I use doesn’t support #warning (although it does support #error). I use “#pragma message” instead, as in
    #pragma message(“WARNING in” __FILE__ “: Code disabled”)

    I then have a script that extracts these messages from the build output and displays them to the console.

  5. Patrick says:

    I usually agree with what you write Nigel but I think this kind of problem (and many others) can be avoided far more elegantly:

    1. Use a proper version control system (i.e. git, mercurial et al.), forget about subversion, cvs etc.
    2. Review your diffs properly.

    When you use something like git you can have branches which are dedicated purely to whatever experimental mood you are in today. Once things are fixed/improved you selectively merge to your main tree only the parts that make sense. You can even choose to keep your debug code around in case you need it later.

    One of my key criteria when assessing an unknown programmer is how thoroughly they review and minimise their diffs.

    • Nigel Jones says:

      An interesting comment Patrick. I’m not familiar with git or mercurial. Consider the case where you are debugging a piece of code and wish to remove / add some temporary test code. In my case I just use a #warning as described in the post. Would you really create a branch every time you wanted to do this? Is git really so easy to use that you can manage the cascade of branches that would occur during a typical debugging session?

      • Patrick says:

        Git has totally changed the way I work. My workflow for fixing a bug generally follows this kind of pattern:

        1. Make sure my working tree is clean — commit or stash any changes.
        2. Create a new branch for the bug fix/investigation
        3. Hack away at the code as much as I want, changing things, commenting code, whatever.
        4. Get interrupted by something else that the boss says is more important. Commit outstanding changes to bug fix branch.
        5. Switch back to master branch and implement/test unrelated work.
        6. Switch back to bug fix branch (there are often a few of these), and keep debugging/instrumenting, committing whenever I feel I’m getting somewhere.
        7. When I feel the bug is fixed I squash my bug fix branch (combine all the commits from the bug fix branch into one) and begin reviewing — depending on how my bug fix branch evolved I may cherry pick certain parts of it (usually obvious fixes) before squashing.
        8. Selectively stage changes that are related to the bug, leaving debug code out.

        At this point I should mention that git has a very useful thing called a staging area. This is the set of changes that are actually going to be committed. When you have a bunch of modified files you can choose to add/remove all or part of any changed file to/from the staging area. You can then diff between working tree and staged, staged and repository, or working tree and repository.

        This allows you to easily split code that’s used for instrumentation from code that’s actually genuine bug fix. I usually find that at least 2/3 of the diff gets dropped at this point.

        9. Finally commit the (hopefully) fixed patch.

        Another amazingly useful utility git provides is called bisect. If you know two points in your change history, one which is bug free, and another which contains a bug, bisect helps you do a binary search of the history to find the exact commit that introduced the bug.

        Now that I’ve said all that, I still do use #warning, but generally only when I go home at night, to remind me where I was last hacking, and also (rarely) to flag unfinished functions. In this case I more often only write the function declaration, with no function definition — that way the code will compile, but won’t link.

        And to finally answer your question I generally use one branch per bug, the branch only lives as long as I’m actively working on the bug, and thus provides me with the separation I need from my stable code.

        With git you can easily manage as many branches as you can remember, however I must say that the learning curve is still quite steep, mainly because it requires a fundamental change in the way you think about version control.

        Patrick

        • Rhys Drummond says:

          Sounds pretty much what I do with Tortoise SVN. I don’t really get the “Use a proper version control system” comment. Subversion works great for me.

          What is annoying is having code that compiles on 2 platforms, one which supports #warning and one which doesn’t… worst case I end up with

          #ifdef _COMPILER_A
          #warning Warning.HI: A Hi level warning
          #else
          #pragma message Warning.HI: A Hi level warning
          #endif

          which is pretty awful. Don’t know other ways around it though. :/

        • Gauthier says:

          I second that the use of git dramatically changed my way of working.

          Relevant to this case:
          – branching is very very cheap (Patrick is right, one branch per feature, per fix, per crazy idea you want to test)
          – merge actually works
          – reviewing diffs is easy
          – you can choose to select or not select the difference blocks (called hunks) and include them in a commit, separately.
          – you can also diff and discard a hunk very easily (see that line that you changed just for test? Discard the hunk and you are back to original).

          The two last points imply that you can diff and commit only the relevant changes, for example you can exclude the changes that comment out code.

          The best that git did for me: operations are so fast and easy that I can produce up to a dozen commits per day. This results in a granularity that is very comfortable. No more “50 issues were fixed since the last commit”. You get one (or more) commit about one issue.

          I guess our workflow results in the same as your #warning, we all catch the code that is commented out. The difference is that you do that by voluntarily introducing warnings in the compiler output, while we get warned upon diffing before committing.

      • Gauthier says:

        Just to clear things up a bit:
        You would not create one branch per part of the code that is commented out.
        You would have one branch for debugging an issue. When you are done with the fix, diff the whole branch with its base, and discard the changes that you do not want (for example commented out code).

  6. Andrew Neil says:

    The fundamental problem, of course, is that #warning is non-standard directive – so not all compilers support it.

    • Gauthier says:

      And that if you comment out 10 parts in your code, you get 10 warnings. You would likely miss this 11th warning when it shows up in the middle of your debugging session.

  7. Rubberman says:

    Your use of #if 0 / #else #warning … / #endif associated with “quit on warning” directives to the compiler for release/test builds is excellent advice. This should go into every company and software engineer’s personal software process rule set.

  8. Lawrence P says:

    Thank you for this great tip! I’ve started to use it in my projects.

  9. Fabio says:

    Is it possible to disable all user-defined warnings (i.e. #warning directives) and leave all the others?

    • Nigel Jones says:

      I would have thought so. If you look at the warning generated with #warning, you’ll typically see a number associated with it. Most compilers allow you to suppress specific warning numbers. The exact syntax varies by compiler.

Leave a Reply