1. 6
  1.  

  2. 10
    #define macro(x) do { foo(x); bar(x); } while(0)
    

    The example macro, as it stands, evaluates x twice, which is a macro faux pas.

    Also, it is ill-advised to use a macro as a statement within another statement aside from a compound statement, regardless of the do {...} while (0) convention. It very likely will mislead the reader into thinking it’s actually an expression. This is a matter of style for the most part, but generally a good one, in my experience.

    1. 1

      very likely will mislead the reader into thinking it’s actually an expression

      Why? Do you never call void functions?

      1. 3

        Sure I do. But then, if you use this macro in an if condition, you’ll be a bit surprised.

        1. 2

          No more so than if you used a function or variable returning void, or a structure. I don’t see as it’s particularly of concern.

          1. 5

            The point is to signal to the reader immediately that they are dealing with something that may only be well defined from a specific context that is beyond what one would expect from the language. Since macros can expand to pretty much anything, it’s helpful to the reader to know that what they are looking at may be very different than what it seems. That’s why the convention of uppercase naming for macros is useful. And also why it’s useful to put them in a context that doesn’t lead you to believe it might be a function and evaluated as such. (A statement that is just foo(); looks more like a function than anything else. But if you write it { foo(); } then it’s more likely to signal to the reader that there may be more going on than what appears to be a function call.)

            And of course that can be violated and of course you’ll find out one way or another. Like I said, it’s a matter of style. I think it’s poor style.

            It also doesn’t help that the original macro is an utterly uncompelling example. If you are writing a macro to do a common sequence of operations, you are better off to use a function (possibly inline) to avoid the problems of text replacement from macros in the first place.

            1. 1

              Yes, I completely agree with you. A function would suit this case better. However, that is not the point of the post.

              1. 2

                What you have is not wrong (although the multiple evaluation is questionable), but the motivation needs to be there for the post to have more impact. What your post describes is a well-known problem in C. The do while (0) approach has been around for a very long time. With the long history of C and the sheer number of lines of code out there, it would probably be better if you found an actual example from real code to make the point.

                Here is one to consider. It’s in GCC and it even uses the the style that I disagree with, although in this case it’s only used in a single function so maybe there’s a better case for it. The history of it shows that it didn’t use the do while (0) pattern at first, which led to a dangling else issue. That has a lot more to it than a foo bar cooked up example. It’s also instructive to show why it is not a function, even though it looks like it could be.

                I understand that you’re probably learning your way through C. That isn’t a problem. However, you may want to cite some actual code to show the interesting (or weird) parts of the language. Code search is actually a reasonable thing these days and there’s no shortage of C code to look at. Good luck!

    2. 3

      The larger a program becomes, the more essential a macro can become.

      Disagree. Macros are hard to read, hard to debug, and easy to mis-use. I don’t think the need for macros rises with the size of the program (but maybe the way a single module has been designed will make it difficult to use without helper macros – fixed by changing the module’s design).

      I avoid defining macros as much as possible when writing C, and it’s worked pretty well so far.

      1. 1

        Our code base has some older parts in C and some macros used there, but the whole program is compiled as c++, recently we made an effort to replace one macro with “modern” c++ (17). Can’t share too much but it was related to a timing critical protocol (multi drop bus) and boy oh boy are macros hard to debug. Code was all nonsense short variable names, buffer (char[250] buf;) reuse everywhere, arrays “cleared” via for loops, but still, worked for over 10 years without much problems. That part of the code is now tripled in lines of code, but so much more expressive, clear and debuggable.

        1. 1

          This problem has been known for nearly half a century, and yet C hasn’t added a fix for it. It’s disappointing that C is frozen in time, because such complications have become a rite of passage: everyone has to trip over the same issue, and learn the same workaround :(

          1. 1

            There is a fix, at least if you can use C99. I’ve forgone macros with static inline functions. You have proper type checking, no issues with i++ type issues, and it’s easier to debug. And at the right optimization level, the functions are inlined (usually—the compiler can treat the inline modifier as a hint).