0062 About Macros ================= Revision History 2001 nov 29 Maarten Pennings Initial version 2002 may 22 Rob Golsteijn Added parenthesis in some #defines Intro ----- Macros allow textual tranformations of the program code. That makes them powerfull and dangerous. This about note explores some common and not so common constructs, and some advise of how to shape your macros. This note is inspired on discussions with Rene Geraets. Most of the examples are found in the infra code base. Typical uses ------------ Macros are used for different purposes. We distinguish 6 categories ("classes"). When you have to write a macro, you should also (first?) decide which class it belongs to. In general, the lower classes are easier to understand and use than the higher ones. The classes are: 1. Constant 2. Pure function 3. State based function without side-effects 4. Function with side-effect 5. Statement 6. Language extension 1. Constant ----------- In its most simple form, a macro is a constant. Examples #define DAYS_IN_YEAR 365 #define MAXPERIOD 40 #define MAX 100 int row[MAX]; for( i=0; i>4) & 0x0F ) #define MAX(a,b) ( (a)>(b)?(a):(b) ) #define IS_POW2(x) ( ((x) & (x)-1) == 0 ) #define ABS(x) ( ((x)<0) ? -(x) : (x) ) // bits 12 and 13 encode the I2C-bus in an address #define I2CBUS( address ) ( (address) & 0x3000 ) Access functions also fall into this class. struct { int id; int parent; int value } * Node; #define VALUE(node) ((node)->value) typedef ULONG (__stdcall *pAddRef)(void * this); typedef struct { pQueryInterface QueryInterface; pAddRef AddRef ; pRelease Release ; } VUnknown; #define IUnknown_AddRef(p) ( (*(p))->AddRef(p) ) Observe that the syntactical convention for such macros is 'name describing a "mathematical" function (or member in case of an access function)'. Since these are mathematical functions, they have arguments. Class 2 macros therfore always have parenthesis. (A pure function without arguments is a constant, i.e. a class 1 macro). It is unusual to find names here like GETMAX(a,b). The GET part suggests that a real operation (a call) has to take place, which is not needed for macros in this category; a MAX suffices. Class 1 and 2 macros can be used at static places, e.g. the size of an array, or the case of a switch. 3. State based function without side-effects -------------------------------------------- This category is nearly the same as the previous one. It also deals with functions that do not have side effects. However, the functions do read-only access to global data. They don't modify the data (state), but the return-value might be different on each call. Such functions are sometimes implemented as macro for speed. int stackval[100]; int stackptr; #define STACKGETTOP() ( stackval[stackptr] ) My suggestion would be that the syntax and naming for class 3 macros follows a real c-function style: so add parenthesis and show in the name that it is more expensive then just some expression juggling. That's why the above example uses ...GET...(). The strange thing is, that the body of the macro in the example actually is an ordinary expression equal to the class 2 bodies. So the GET part might be a bit overdone. This is confirmed when we look at real infra examples: #define COOL_DOWN_TIME ( 10 + rtk_TmrResolution() ) #define ADOC_BUILD ( gio_iPresent() && reg_iPresent() ) All these macro names suggest to be ordinary class 1 macros (constants) whereas they actually make a function call, even one that is not constant. So according to my own suggestion the "cool down macro" should have been called GET_COOL_DOWN_TIME(). Apparently, for the developers of these macros, the class 1 feeling ("this macro should be thought of as a constant") prevaled over the class 3 feeling ("this is an expensive function call"). For the ADOC_BUILD macro, this makes sense, because in most cases, the iPresent() functions are actually class 1 macros! 4. Function with side-effect ---------------------------- A macro could also mimic a function with side effects. Some people might call these procedure-look-alikes (but they are definitely not statement-look-alikes, see below). However, the crux of class 4 macros is that they behave like functions having side-effects, not that their return type is void. Referring to the stack example from class 3 we could have #define STACKINIT() (stackptr=-1) #define STACKPUSH(val) (stackval[++stackptr]=(val)) A less clear example of this category is #define ASSERT(p) ( (p) ? 0 : exit(1) ) Since exit() has a side effect this macro has a side-effect. If exit(1) has type void, and/or one wants ASSERT to be more like a procedure one could change this to #define ASSERT(p) ( (p) ? (void)0 : exit(1) ) One should note that the macros in this category are implemented as expressions (with side effects). So in the good old c tradition they can be made into a statement by terminating them with a ';' but they are not yet statements; they may be embedded in an expression. So, it is common to see ASSERT( size>0 ); but the following also works because the above ASSERT is an expression #define BIT(v,p) ( ASSERT( 0<=(p) && (p)<32 ) , ((v)>>(p)) & 0x01 ) Observe that the syntactical convention for such macros is 'name describing an action'. Also note that class-4 macros should use parenthesis (as in STACKINIT() ). So, the following example (from real code) violates two aspects. int tst_state; #define TST_INIT (tst_state=1) #define TST_INPUT (tst_state=2) #define TST_EVENT (tst_state=3) These macros should have been called TST_SET_INPUT(), the ...SET... to stress the action, the () to stress the functional (side-effect) behavior. 5. Statement ------------ A macro could be implemented as a statement; not an expression. For example, one could have defined ASSERT as #define ASSERT(p) if( !(p) ) exit(1); /* not wise syntax, see below */ Note that the BIT example above won't work anymore! In general, it is wise to try making a class 4 macro instead of a class 5 macro. They have the same mental complexity, but they give the user more freedom. When making a class 5 macro, make sure that it syntactically behaves like a statement. So give it a 'name describing an action' and give it parenthesis (to stress the expensiveness) and make sure it follows the c conventions of statement-placing and semicolon-placing. The latter deserves some explanation. Suppose we want to concat two statements into one, and design the macro #define Both() f();g() This is incorrect because if( FALSE ) Both(); still executes g(). The next try still has some flaws #define Both() { f();g(); } the following line does not compile! if( expr ) Both(); else Nothing(); because it expands to if( expr ) {f();g();} ; else Nothing(); that is, two statements in the then-branch, so that the else-branch is not allowed. The actual "bug" here is in the c-language: all statements end with a semicolon, except one, the block statement. This can be fixed in only one way (and is thus "well-known"); use the do-while to "swallow the semicolon": #define Both() do { f();g(); } while(0) So, the preferred way to make a skip statement is #define skip() do ; while(0) Note, c requires a single statement in the do-while, hence the ';', but the following also works. #define skip() do { } while(0) The following three variants all break down one day or another. #define skip /* 1 */ #define skip ; /* 2 */ #define skip {} /* 3 */ Solution 1 breaks in if( expr ) skip x=3; and solution 2 and 3 break in if( expr ) skip; else x=3; However, the preferred way to implement skip is as a void expression, not a statement: #define skip() ((void)0) or #define skip ((void)0) These are, by the way, class 6 macros "language extensions". Why would one write a class 5 instead of a class 4 macro? In my opinion, one writes a class 5 macro instead of a class 4 macro only when one really needs something that is not available in the expression domain. Sequencing and selection exists (with ',' respectively '?:'). So the only reason I would introduce a class 5 macro is when needing a block (for an extra temporary variable), when needing a sub-statement, or when needing repetition. /* need an extra var */ #define SWAP(a,b) do { int temp=a; a=b; b=temp; } while (0) /* need a statement internally "return" */ #define EXCEPT(a,b) do { if((a)<(b)) return (b)-(a); } while (0) /* need a repetition */ #define SKIP_SPACES(p,limit) \ do \ { \ register char * lim = (limit); \ while( p!=lim ) \ { \ if( *p++ != ' ' ) \ { \ p--; \ break; \ } \ } \ } while( 0 ) Why is the local var lim introduced? Probably because a call might be something like SKIP_SPACES( p, q++ ) and the ++ on q should only be executed once! 6. Language extension --------------------- The last category is also the hardest one to describe. Macros are often used to implement patterns from "higher level" langauges (e.g. try..except or templates), to help bridge language differences (e.g. COM interface definitions that should compile in C and C++), or to perform an assortment of hacks. #define LASTERROR ( *( (Nat16*)rtk_TlsAccess(tls) ) ) This macro names suggest to be ordinary class 1 macros (constants) whereas it actually makes a function call, even one that is not constant. But it is not only used to get the last error printf("%i",LASTERROR) but also to set the last error LASTERROR=2; So, the developer want this macro to be thought of as an ordinary variable. Another famous language extension is try-catch using the c setjmp/longjmp functions. The macros TRY, CATCH, DONE should be thought of as language keywords just as in do-while. #include #define MAX_TRY_NESTING 20 extern int level=0; extern jmp_buf tryspot[MAX_TRY_NESTING]={0}; #define TRY if( setjmp(tryspot[level++])==0 ) { #define CATCH level--; } else { #define DONE } #define THROW longjmp( tryspot[--level],1); void g() { ... THROW; } void f() { TRY g(); CATCH printf("CAUGHT"); DONE } Other examples // There are memory mapped registers LCR0, LCR1, LCR2 Nat8 LCR0 _at( 0x509 ); Nat8 LCR1 _at( 0x50A ); Nat8 LCR2 _at( 0x50B ); // and a single access routine #define SETLCR(i, t0, t1) LCR##i = (((Nat8)(t0))<<4) | (Nat8)(t1) // A table of ITEM's is processed several times, // each time with a different definition of ITEM #define ITEM(name,devid,addr,type) #name // Sometimes the preprocessor has a strange evaluation order. // It then helps to add a macro wrapper #define STRINGIFY(s) #s #define CONCAT(a,b) a##b (end of about note)