Path: utzoo!utgpu!news-server.csri.toronto.edu!mailrus!uunet!mcsun!unido!mikros!mwtech!martin From: martin@mwtech.UUCP (Martin Weitzel) Newsgroups: comp.lang.c Subject: Re: Using Macros Message-ID: <878@mwtech.UUCP> Date: 8 Aug 90 13:34:33 GMT References: <362.26be9dcc@astro.pc.ab.com> Reply-To: martin@mwtech.UUCP (Martin Weitzel) Organization: MIKROS Systemware, Darmstadt/W-Germany Lines: 157 In article <362.26be9dcc@astro.pc.ab.com> yoke@astro.pc.ab.com (Michael Yoke) writes: [question about how to define multiple statement macros that can be used much like functions, esp. *require* a semicolon after their invocation] My first recommendation: Don't write such macros, use a function. My second recommendation: Same as first. Still reading on? So you seem to be pretty sure that you *must* do it with macro. So read my third recommendation, but read it carefully and completly: The trick is to embrace the statements with something that needs a trailing semicolon. Basically, there are two such methods. #define multi_stmt_macro1() if (1) { put your stuff in here } else or #define multi_stmt_macro2() do { put your stuff in here } while (0) NOTE: In both cases you write *no* trailing semicolon in the #define. So an invocation of this macro must supply the semicolon: multi_stmt_macro1() ; or multi_stmt_macro2() ; expands exactly to one execution of the statements you have written in the definition. There is one reason why the second form is preferable. Consider the case, where you inadvertently forgot to supply the trailing semicolon. multi_stmt_macro1() /* ; forgotten here */ x = y; The following statement (here: x = y;) will *never* be executed (some compilers and lint may warn you, but if you prefer `super-silent' compilations - or lint-ations - you have possible put some fake-stuff in or around your macro so that you still get no message). On the other hand multi_stmt_macro2() /* ; forgotten here */ x = y; gives you what you deserve: A syntax error. The above way to define a macro is the "most function-like" I know, but it has still some drawbacks. The most important is that you should DESCRIBE THE TRICK IN A COMMENT, so that the poor programmer who has to maintain your code one day will not have too hard a time to understand what you have done. Because of that I prefer using the trick in two steps, as the following code excerpt shows: /* * The following definitions of BMAC and EMAC are supplied for the * purpose to write multi statement macros that resemble true * functions as close as possible. Note that the flow-control * statments here have the sole purpose to require a trailing * semicolon after the invocation! */ #define BMAC do{ #define EMAC }while(0) /* * Use BMAC to start the definition of a multi statement macro. * Use EMAC to end the definition: Here is an example: * * #define MULTI_STMT_MACRO(macro_parameters) BMAC\ * stmt;\ * stmt;\ * EMAC */ ................ many lines ................ may be here #define A_MACRO() BMAC .............. EMAC ................. again many lines #define B_MACRO(a,b) BMAC\ ............\ ............\ EMAC The big advantage here comes when a later maintainer reads the code. Generally, maintenance-reading may start anywhere in the middle, and the programmer will eventually need to look what A_MACRO really does. After locating the #define A_MACRO, he or she finds two other things that are probably not known: BMAC and EMAC. Locating these two directly stumps onto the comment which explains their purpose. So you have only *one* place where you can put your explanations, even if you use the trick several times. Furthermore, the example in the comment near the definition of BMAC and EMAC will help a maintainer who reads the code "top down" and encounters BMAC and EMAC before he or she has seen the actual usage. Finally some words to the wise: Those block macros are somehow "poor man's inline function". They have all the disadvantages of inline code (more space, no recursion, ... - of course they have advantages too, otherwise you would not use them) *plus* the common disadvantages of macros vs. functions. Besides that they may not return a value important pitfalls are: 1) Macro arguments you write more than once in the replacement text are expanded more than once, so side effects may take place more than once, WHICH IS NOT AT ALL VISIBLE FROM THE INVOCATION! (We just tried to make the invocation look like a function.) 2) If you need local variables, you must avoid to give the same names as parameters in the invocation. (See following example). As I allready recommended, such macros should not be used very often. In fact, I can see only two places where they sometimes may be desirable: a) You have determined with some profiling tool that the overhead for a certain function call is too expensive compared to the work the function does and you want to "inline" the function code as painless as possible (or maybe choose between a true function and inlining with a #define Option at compile time). b) You have a common algorithm which you want to make "type independent". In both cases you should ensure that the invocation takes care to avoid the pitfalls described under 1) and 2). I usually choose upper-case-only names for such pseudo-functions. At least a moderatly experienced later maintainer will be warned thru that, as C-functions traditionally use lower-case-only or mixed-case names. Finally I have a `real life' example for b) - hopefully without typos :-) #define PRINTARRAY(fmt, npl, arr) BMAC\ int _i_ = 0;\ while (_i_ < sizeof arr / sizeof arr[0]) {\ printf(fmt, arr[_i_]);\ putchar((++_i_ % npl) ? ' ' : '\n');\ }\ EMAC With this macro you can print any(%) array "arr" as long as it consists of elements for which you can give a printf-format specifier "fmt". You get "npl" elements per line (seperated by a blank). The only problem with this is that you can NOT print an array named "_i_". (Of course this is the reason why I gave the loop counter this strange name and didn't call it "i" or somthing similar common. %: Note that with `T a[100][200]' and `struct { T b[100]; } s' PRINTARRAY still works for `a[33]' and `s.b', but that it fails in more complex cases, eg. with `T z[100]' for `z + 1', which is still valid for C, but produces a syntax error when it expands to `sizeof z + 1 / sizeof z + 1[0]'. The syntax error could be avoided by enclosing `arr' in brackets - as it is generally advisable in macro replacement text - but in the above case I prefered the failing over getting a completly meaningless results (meaningless for my purpose). Of course, a clever programmer might specify `1 + z' which causes no syntax error but will fool PRINTARRAY badly. -- Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83