Sensei said:
...this is puzzling to me. I've seen functions replaced by macros, like
this:
#define xge_mklink_slot(com,from,to,slotfrom,slotto) \
{\
tkey _slotfrom=(slotfrom);\
tkey _slotto =(slotto );\
\
CHECKPOINT("ie",getcell((com),(from)).level!=0xff &&
getcell((com),(to)).level!=0xff);\
\
if (!_slotfrom)\
{\
if (!(com)->memlink.freelist) realloc_link((com));\
_slotfrom=(com)->memlink.freelist;\
(com)->memlink.freelist=getlink((com),_slotfrom).next;\
++((com)->memlink.num);\
\
if (!getcell((com),(from)).nup)\
getcell((com),(from)).firstup=_slotfrom;\
else\
getlink((com),getcell((com),(from)).lastup).next=_slotfrom;\
\
getlink((com),_slotfrom).next=0;\
getlink((com),_slotfrom).prev=getcell((com),(from)).lastup; \
getcell((com),(from)).lastup=_slotfrom;\
++getcell((com),(from)).nup;\
}\
\
if (!_slotto)\
...........
This is absolutely horrible. Words fail me. Whoever wrote this should be
shot.
Let's go over why this is a bad idea:
- In a macro, arguments are not checked against their types, as there
are no types. This means that the function is both harder to call ("com"
must be a pointer, but of what type?) and the compiler can offer less
help checking validity.
- Generalizing, the validity of a macro simply cannot be checked on its
own. Only uses of it can be checked. Errors or warnings that occur from
such a use could be either due to bad use or a bad macro, and it'll be
hard to tell which is which.
- Unlike functions, macros use textual replacement for their arguments.
If an argument uses a side-effect (it modifies the program state when
evaluated) then you typically cannot use it. For example:
xge_mklink_slot(c, f++, t++, slf, slt);
This does not have the same effect as the equivalent function call
would: every time "from" is replaced in the macro it is replaced with
"f++". That is, f will be incremented every time "from" is used!
- This is very likely to *harm* performance. Yes, you read that right.
If this macro is invoked multiple times, the great big block of code it
stands for is inserted *every time*. Do this a few times and you'll get
code that is huge. And WHY? Function calls are not nearly as expensive
as most people like to pretend. It is in fact likely this code will run
slower because big code means the instruction cache most modern
processors are equipped with cannot be effectively used. Never try to
outsmart the compiler. Only educate it in things you know it cannot see.
Making decisions on when to inline functions is something a compiler is
qualified to do, and only code profiling should be admissible evidence
to the contrary.
Apropos inlining: almost all compilers I know offer a way to indicate
that inlining is desirable (or even force it where possible). The new
C99 standard adds an "inline" keyword, and most pre-C99 compilers
support an "inline", "_inline", "__inline" keyword or some variation
thereof. Macros are almost never necessary or desirable to get an
inlined function.
All that said, there is one purpose this macro could still serve that
cannot be achieved otherwise in C: precisely because the arguments have
no types, it can be used as a template to produce similar functions that
differ only in the type of the arguments. This is what templates
accomplish in C++ and generics accomplish in the most recent versions of
Java.
But even so, it should only be used in a way that does not compromise
type safety, by defining the type-safe functions:
/* Unlike XGE_MKLINK_SLOT, the compiler will warn or stop if the
argument types do not match the formal parameter types. */
[return-type] xge_mklink_slot(xge_com* com, xge_cell from, xge_cell to,
tkey slotfrom, tkey slotto) {
return XGE_MKLINK_SLOT(com, from, to, slotfrom, slotto);
}
Here [return-type] I cannot know, and the xge_com and xge_cell types are
invented by me. I capitalized XGE_MKLINK_SLOT because macro names
*should* be capitalized to prevent confusion: as I've said, using a
macro is not the same thing as calling a function, and it's dangerous to
pretend that it is.
Even this technique is dubious because xge_mklink_slot is a highly
specific macro, and it's unlikely it's used in the way I sketched above.
Note also that in C, generic functions are usually written by passing
around generic pointers. Consult your C book for more information; it
will probably mention the qsort() function as an example.
This macro *should* be rewritten to a function. I cannot conceive of any
reason why a macro would be preferrable in this case. Do you understand
why I said it's not a matter of coding style? It's a matter of what you
want to achieve, not how it looks. "Style", if it comes into play at
all, is to prefer functions to macros consistently, and being ready to
justify your use of a macro.
> So, I was wondering why and when it's a good idea to use defines.
>
Don't think in terms of "good idea/bad idea". Learn what purpose and
what drawbacks each feature has, then apply this knowledge in every case
to achieve the best result. In case of macros, the drawbacks associated
with them very often outweigh the benefits, especially when it comes to
replacing functions.
I can give you the short version, of course, but keep in mind that these
are only rules of thumb and not absolute commandments. Use a macro for:
- defining symbolic constants that are not part of an enumerated type.
Example: #define M_PI 3.1415926536
- encapsulating system-specific constructs that cannot be abstracted by
a function. Example: #define INLINE __inline
- allowing header files to be included more than once without error.
Example:
#ifndef HEADER_H
#define HEADER_H
...contents of "header.h"
#endif
These are the basic established "good ideas" for macros. There are other
circumstances in which experienced C programmers will use macros, but
you should leave them until you can understand why. I won't attempt to
explain it all in a Usenet post; consult the books written by people
who've had more time than me to think about it.
S.