Monday, March 26, 2012

Paratmetrized and Multiline Macros in C

Paratmetrised Macros:
The #define may have arguments. For example, the following macro doubles a number:
/* Double a number */
#define DOUBLE_IT(number) (2 * (number))
Enclosing the entire macro in parenthesis avoids a lot of trouble similar to the problems with simple #define s.
Enclose parameterized macros in parentheses.
In the next example, the macro SQUARE is supposed to square a number:
/* Square a number */
#define SQUARE(X) (x * x)
/* Bad practice, no () around parameter */
The invocation of the macro:
a = SQUARE(1 + 3);
expands to:
a = (1 + 3 * 1 + 3);
which is not what was expected. If the macro is defined as:
/* Square a number */
#define SQUARE(X) ((x) * (x))
Then the expansion will be:
a = ((l + 3) * (1 + 3));
Enclose each argument to a parameterized macro in parenthesis.

Multiline Macros:
The #define statement can be used to define code as well as constants. For example:
/* Print current values of registers (for debugging) */
#define PRINT_REGS printf("Registers AX=%x BX=%x\n", AX,BX);
This is fine as long as the target of the #define is a single C statement. Problems occur when multiple statements are defined. The following example defines a macro ABORT that will print a message and exit the system. But it doesn't work when put inside an if statement.
/* Fatal error found, get out of here */
#define ABORT print("Abort\n"); exit(8);
/*.... */
if (value > LIM)
    ABORT;
problem can easily be seen when we expand the macro:
if (value > LIM)
    printf("Abort\n"); exit(8);
Properly indented, this is:
if (value > LIM)
    printf("Abort\n");
exit(8);
This is obviously not what the programmer intended. A solution is to enclose multiple statements in braces.
/* Fatal error found, get out of here */
#define ABORT { printf ( "Abort\n" ); exit(8) ; )
    /* Better, but not good */
This allows you to use the ABORT macro in an if, like this:
if (value > LIMIT)
    ABORT;
Unfortunately, it causes a syntax error when used with an else:
if (value > LIMIT)
    ABORT;
else
    do_it();
The do/while statement comes to the rescue. The statement:
do {
    printf( "Abort\n" );
    exit(8);
} while (0);
executes the body of the loop once and exits. C treats the entire do/while as a single statement, so it's legal inside a if/else set.
Therefore, properly defined, the macro is:
/* Print an error message and get out */
#define ABORT                             \
    do {                                  \
        printf( "Abort\n" );                \
        exit(8);                          \
} while (0)                      /* Note: No semicolon */
Always enclose macros that define multiple C statements in braces.
If a macro contains more than one statement, use a do/while structure to enclose the macro. (Don't forget to leave out the semicolon of the statement).
When macros grow too long, they can be split up into many lines. The preprocessor uses the backslash ( \ ) to indicate "continue on next line." The latest ABORT macro also uses this feature.
Always stack the backslashes in a column. Try and spot the missing backslash in the following two examples:
/* A broken macro */
#define ABORT                             \
    do {                                  
        printf( "Abort\n" );                \
        exit(8);                          \
} while (0)
/* Another broken macro */
#define ABORT \
    do { \
        printf( "Abort\n" ); \
        exit(8); 
} while (0)
The mistake in the first example is obvious. In the second example, the problem is hidden.
When creating multi-line macros, align the backslash continuation characters ( \ ) in a column.

No comments:

Post a Comment