For example, never define a macro like this:
#define DANGER 60 + 2
This can potentially be dangerous when we do an operation like this:
int wrong_value = DANGER * 2; // Expecting 124
Instead, define like this because you don't know how the user of the macro may use it:
#define HARMLESS (60 + 2)
The example is trivial, but that pretty much explains my question. Are there any set of guidelines or best practices that you would recommend when writing a macro?
Thanks for your time!
A macro is a fragment of code that is given a name. You can define a macro in C using the #define preprocessor directive. Here's an example. Here, when we use c in our program, it is replaced with 299792458 .
Speed versus size The main benefit of using macros is faster execution time. During preprocessing, a macro is expanded (replaced by its definition) inline each time it is used. A function definition occurs only once regardless of how many times it is called.
A macro (which stands for "macroinstruction") is a programmable pattern which translates a certain sequence of input into a preset sequence of output. Macros can make tasks less repetitive by representing a complicated sequence of keystrokes, mouse movements, commands, or other types of input.
Not only should you put parens around the arguments, you should put parens around the expression returned.
#define MIN(a,b) a < b ? a : b // WRONG
int i = MIN(1,2); // works
int i = MIN(1,1+1); // breaks
#define MIN(a,b) (a) < (b) ? (a) : (b) // STILL WRONG
int i = MIN(1,2); // works
int i = MIN(1,1+1); // now works
int i = MIN(1,2) + 1; // breaks
#define MIN(a,b) ((a) < (b) ? (a) : (b)) // GOOD
int i = MIN(1,2); // works
int i = MIN(1,1+1); // now works
int i = MIN(1,2) + 1; // works
However, MIN(3,i++)
is still broken...
The best rule is only to use #defines only when NO OTHER APPROACH WILL WORK! I know you're asking about C instead of C++, but still bear his in mind.
When doing a macro that is to run its argument and behave like an expression, this is idiomatic:
#define DOIT(x) do { x } while(0)
This form has the following advantages:
Use parenthesis around the entire macro and around each argument referred to in the expansion list:
#define MAX(x, y) ((x) > (y) ? (x) : (y))
Avoid writing macros that evaluate their arguments multiple times. Such macros will not behave as expected when arguments have side effects:
MAX(a++, b);
Will evaluate a++
twice if a
is greater than b
.
Use UPPERCASE names for macros to make it clear it is a macro and not a function so that the differences can be considered accordingly (another general good practice is not passing arguments that have side effects to functions either).
Don't use macros to rename types like this:
#define pint int *
because it won't behave as expected when someone types
pint a, b;
Use typedefs instead.
use static const values instead of macros for constant values, integral or other. The compiler can often optimize them away, and they remain a 1-st class citizen in the language's type system.
static const int DANGER = 60 + 2;
In the expansion, put parenthesis around the arguments, so that if they pass in a expression you will get the intended behavior.
#define LESS_THAN(X,Y) (((X) < (Y) ? (X) : (Y))
Response to the MAX/MIN macros, taken from GCC hacks in the Linux kernel:
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
Use fairly unique names for your macros, since they have global scope and can clash with anything, so:
#define MAX 10
could easily clash with other code, so:
#define MYPROJECT_MAX 10
or something even more unique, would be better.
I've seen cases where a clash of this kind didn't produce a compile error, but generated slightly wrong code, so it can be quite insidious.
For multiple line macros, use a do { } while (0)
:
#define foo(x) do { \
(x)++; \
printf("%d", x); \
} while(0)
Had you done
#define foo(x) { \
(x)++; \
printf("%d", x); \
}
instead,
if (xyz)
foo(y);
else
foo(z);
would've failed.
Also, be careful when introducing temporary variables in macros:
#define foo(t) do { \
int x = (t); \
printf("%d\n", x); \
} while(0)
int x = 42;
foo(x);
will print 0
and not 42
.
If you have a complicated expression which needs to return a value, you can use the comma operator:
#define allocate(foo, len) (foo->tmp = foo->head, foo->head += len, foo->tmp)
Undefine your macros.
Your #defines
should matched with an #undef
. This prevents the preprocessor from getting clogged up and affecting unintended pieces of code.
If you're careful and expert, you may be able to accomplish DRY (Don't-Repeat-Yourself) code, by using macros as simple code generators. You do have to explain to other programmers what you're doing, but it can save a lot of code. For example, the list-macro technique:
// define a list of variables, error messages, opcodes
// or anything that you have to write multiple things about
#define VARLIST \
DEFVAR(int, A, 1) \
DEFVAR(double, B, 2) \
DEFVAR(int, C, 3) \
// declare the variables
#define DEFVAR(typ, name, val) typ name = (val);
VARLIST
#undef DEFVAR
// write a routine to set a variable by name
void SetVar(string varname, double value){
if (0);
#define DEFVAR(typ, name, val) else if (varname == #name) name = value;
VARLIST
#undef DEFVAR
else printf("unrecognized variable %s\n", varname);
}
// write a routine to get a variable's value, given its name
// .. you do it ..
Now, if you want to add a new variable, delete one, or rename one, it's a 1-line edit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With