Macros are not evil. Some people who love
C++ too much will try and tell you that you should never use a macro. That
is just wrong, macros when used correctly can do many things that inline
functions can't (yet) do and can result in code that is easier to write
and (more importantly) easier to read. For example try re-writing the following
in an equally efficient fashion without macros:
int write(char *buf, int len); // prototype for a function to write some data
#define WRITESTR(XXbuf) write((XXbuf), sizeof(XXbuf))
struct FOO
{
char item1[4];
char item2[8];
};
int main()
{
FOO object;
WRITESTR(object.item1);
WRITESTR(object.item2);
}
As you can see if I change the size of item1 or item2 I don't have to go
through my code and change buffer offsets if I have to change the size
of objects in the struct. I used code very similar in concept to this a
couple of years ago on a bank project. The code wasn't as simple as this,
but the project wasn't easy either. The result was that the (almost daily)
changes to the data structures returned from the mainframe were quickly
incorporated into my code. My aim was that when one of the
mainframe people informed me of a change in the sizes of the structure
elements I would have the change coded and compiled before he had
walked back to his desk (I usually achieved that aim).
This is only one example of a good use of a macro. I could provide
a dozen other ways that macros can be used to write code that is easier
to read, understand, and maintain.
Now here's what you shouldn't do with macros:
- Firstly if you have a macro that acts like a function (IE it has code in
it as apposed to the "#define abc 1" type of macro that gets the most use)
then call it in a way that looks like a function. Call it with parenthesis
after it's name (even if it takes no parameters) and then put a semi-colon
after it. This will avoid confusion for the next programmer who works on
your code, and if you use an automatic source reformatter such as ALT-F8
in VC it won't get confused.
- Don't ever have a macro that has a "return" statement. There's nothing
more annoying when you're trying to read the code of an unskilled programmer
and you have to do a search on the contents of every second line of code
to determine if it's a macro, and if so whether it does a return.
If a line of code is setting variables or doing data IO I can generally
get the idea of what the code is doing without reading the source of the
function, with a well written macro it's the same. If the macro may
be doing a return then the code is much harder to read and productivity
goes down.
- Macros should never use local variables in your function unless they are
defined locally - even then it's usually a bad idea. If they allocate their
own variables (something you don't want to do if you can avoid it) then
they should start with XX or some other code that makes a name clash with
macro parameters very unlikely. There are situations (such as code
involving varargs) where a macro defined in the local source file which
uses variables in the function can save a lot of duplicated code and save
the programmer time. That's OK. But having source in a dozen
modules having variables with particular names for macros to use makes
for code that's difficult to maintain.
- Put parenthesis around all macro parameters. If the macro expands to a
numeric expression then also put parenthesis around the macro itself. See
the following:
#define MACRO(XX) XX*2
a = MACRO(b + 1); // expands to "a = b + 1 * 2" not "a = (b + 1) * 2" as we want
#define MACRO2(XX) XX +1
c = MACRO2(d) * 2; // expands to "c = d + 1 * 2" not "c = (d + 1) * 2"
Here's the correct way to define those 2 macros and avoid nasty surprises:
#define MACRO(XX) ( (XX)*2 )
#define MACRO2(XX) ( (XX)+1 )
- When defining a macro that expands to multiple statements then it's good
to do something like the following example from the include/linux/sched.h
in the source to the Linux kernel 2.1.99:
#define SET_LINKS(p) do { \
(p)->next_task = &init_task; \
(p)->prev_task = init_task.prev_task; \
init_task.prev_task->next_task = (p); \
init_task.prev_task = (p); \
(p)->p_ysptr = NULL; \
if (((p)->p_osptr = (p)->p_pptr->p_cptr) != NULL) \
(p)->p_osptr->p_ysptr = p; \
(p)->p_pptr->p_cptr = p; \
} while (0)
That code allows the caller of the macro to write this sort of code:
if(a)
SET_LINKS(a);
else
SET_LINKS(B);
So the macro can be used exactly as a function would be. If you do what
most programmers do and just define the macro to expand to a list of statements
then it'll totally fail in the above example. However you can just use
blocks in all if conditions to ensure that the program will still work
even if a single line expands out to many statements, and I wouldn't criticise
someone for coding in such a fashion.
There was a bug in this web page (IE a sample of C code which would not
compile). Thanks to Robert Wall for noticing this and reporting it to me.
I wish that someone had told me about this ages ago!
Copyright © 2000 Russell
Coker, may be distributed freely.