Using macros

Macros allow you to assign a meaningful identifier to a constant, keyword, or commonly used statement or expression. Macros are commonly used in C and C++ programs to provide consistent referencing and use of small code fragments.

Macros are defined in code by using of the #define preprocessor directive. Because of their resemblance to definitions of code objects or functions, macros are categorized as object-like or function-like. The following sample illustrates how to define an object-like and function-like macro:

// Object-like macro
// #define <identifier> <token list>
#define AREA 51

// Function-like macro
// #define <identifier>(<parameters>) <token list>
#define CUBE(x) ((x)*(x)*(x))

You call a macro using the macro's identifier.

int totalArea = area1 + AREA;
int cubeResult = CUBE(57);

During the initial stages of compilation, the preprocessor performs macro expansion that translates each macro call by directly substituting the call with the macro definition and any parameters supplied. This expansion process is similar to a copy-and-paste operation. When compared to a semantically equivalent function call, inline macro expansion can offer some performance benefits.

Using macro expansion can cause subtle errors that can lead various security impacts. You can avoid these errors by understanding the complexities of macro expansion rules and the typical pitfalls affecting macros. While there are several predefined macros in C and C++, these guidelines focus on safely adding your own macro definitions to your code.

Best practices

You should consider the following best practices to safely add your own macro definitions to your code:

  • For function-like macros, don't include whitespace between the macro identifier and the opening parenthesis that encompasses the incoming parameters.
  • Because macros are type-agnostic, design your macro carefully to handle all possible types of macro parameters that could be used.
  • If a macro is expected to return a value and can be used as part of a larger statement, surround the macro completely in parentheses (()).
  • If you are using a macro in an if statement, surround the macro in curly braces ({}). If the statement includes an else, use a loop statement in the macro that can safely be followed by a trailing semicolon.
  • Check parentheses in macros carefully to make sure that they are balanced and that the macro evaluation does not extend further into the source code than intended.
  • Avoid using parameters that contain side effects in a macro. Perform equivalent operations manually in the code before or after macro calls are made.
  • Consider the effects of self-referential macros and their expansion when designing macros.
  • Make sure that macros are assigned unique names that do not conflict with reserved keywords or variable names.
  • Make sure that you check preprocessor documentation for the compiler family you are using because directives, keywords, and handling vary from preprocessor to preprocessor.

Whitespace

Function-like macros must not have any whitespace between the macro identifier and the opening parenthesis that encompasses the incoming parameters. If whitespace is included, the preprocessor treats the function-like macro as an object-like macro instead.

You should use caution when reusing and defining macros because the preprocessor might not treat whitespace as expected. This is especially important in large source files, where other symbol definitions might inadvertently affect the macro expansion in the preprocessor.

The following code sample defines two similar macros, a and b. While these macros appear similar, macro a is treated as a function-like macro, and macro b as an object-like macro. Macro b uses the value of the global integer x in its calculation.

// a global defined earlier in the file
int x = 8;
…
// macro definitions
#define a(x) - 1 / (x)
#define b (x) -1 / (x)

This use of x is made clear when calls are made to the macros:

double d1 = a(8.0);
double d2 = b;

d1 = -0.125
d2 = 8.0

Operator precedence

The usual C/C++ rules of operator precedence apply during macro expansion. These rules mean that macros must be carefully designed to handle all possible types of macro parameters that can be used. This consideration is especially important because macros are type-agnostic.

For example, consider the following CUBE macro and the subsequent call to that macro:

#define CUBE(x) (x*x*x)
…
int x = CUBE(4+5);

In this case, you might expect that x would contain the value 729 (9 x 9 x 9). However, after evaluation, x contains the value 49. This is because of the way the expansion of compound parameters occurs in macros, coupled with operator precedence. In the previous call, the CUBE macro expands as follows:

CUBE(4+5) = (4+5*4+5*4+5)
          = (4+20+20+5)
          = 49

You can avoid this case by forcing compound parameters to be evaluated first, before the macro calculation is performed. Using the example above, you can achieve this by redefining the CUBE macro to correctly handle compound parameters.

#define CUBE(x) ((x)*(x)*(x))

With this change, the macro expands as follows:

CUBE(4+5) = ((4+5)*(4+5)*(4+5))
          = (9*9*9)
          = 729

Embedding macros in larger statements

Macros can be problematic if they return a value and are embedded in other statements.

Consider the following macro that adds 5 to the supplied parameter, and the subsequent call that embeds this macro in a larger statement:

#define ADDFIVE(x) x + 5
…
int y = ADDFIVE(4) * 3;

In this case, y contains the result 19, not the expected 27 (9 x 3). This result is because of operator precedence. The macro actually expands as follows:

ADDFIVE(4) * 3 = 4 + 5 * 3
               = 4 + 15
               = 19

To protect against this problem, if the macro is expected to return a value and can be used as part of a larger statement, surround the macro completely in parentheses.

#define ADDFIVE(x) = ((x) + 5)

With this change, the macro expands as follows:

ADDFIVE(4) * 3 = ((4) + 5) * 3
               = 9 * 3
               = 27

Macros and if statements

Macro definitions often contain only a single line of code or a single statement. However, it's possible to define macros that contain several statements within a single definition.

Consider the following SWAP macro and an if statement that uses this macro to swap the contents of its parameters. You might expect that a swapping operation does not occur, because a is greater than 0, and that a=4 and b=7.

#define SWAP(a, b)  a ^= b; b ^= a; a ^= b;
…
int a = 4;
int b = 7;

if (a < 0)
SWAP(a,b);

However, in reality the results are a=7 and b=3. This is because only the first statement of the multi-statement macro is governed by the if condition and that causes the macro to expand differently than you might expect. In the expansion, the two remaining statements of the SWAP macro execute unconditionally.

if (a < 0)
	a ^= b;
b ^= a;
a ^= b;

You can solve this problem by surrounding the macro in braces ({}). This enforces the grouping of all statements.

#define SWAP(a, b)  { a ^= b; b ^= a; a ^= b; }

However, this solution works only for if statements that do not contain an adjoined else clause.

if (a < 0)
	SWAP(a,b); else
 	… =

In this case, the final semicolon after the SWAP call forces the macro expansion to appear as follows:

if (a < 0)
	{ a ^= b; b ^= a; a ^= b; };
else
	…

You can avoid this by using a loop statement in the SWAP macro that can safely be followed by a trailing semicolon.

#define SWAP(a, b)  do { a ^= b; b ^= a; a ^= b; } while (0)

Balancing parentheses

Parameter substitution in macro expansion follows a specific logic. Arguments are substituted into the macro, and the result and the rest of the input file are then checked for more macro calls. Because of this logic, it's possible to define valid macros that do not have balanced parentheses. This approach can syntactically change the macro from the intended effect. You should check parentheses in macros carefully to make sure that they are balanced and that the macro evaluation does not extend further into the source code than intended. For an example macro that uses unbalanced parentheses, see the GCC online documentation.

Multiple evaluation

Because macro expansion performs a direct copy-and-paste of the macro parameter into the definition of the macro, problems can occur if the parameter also performs an operation.

In the following code sample, you might expect that the macro would be evaluated using the parameter values of 5 and 87, post incrementing x and y, and assign 87 to z.

#define MAX(a, b) ( (a) < (b) ? (b) : (a) )
…
int x = 5;
int y = 87;
…
int z = MAX(x++, y++);

However, the following macro expansion occurs, evaluating y++ twice. So y is post incremented twice, leaving x = 6, y = 89 and z = 88.

int z = MAX(x++, y++); = ( x++ < y++ ? y++ : x++)

A similar effect also occurs in this situation if the parameters contain function calls, as can be seen with the foo() function call in the following sample. Again, a duplication effect occurs, with foo(y) being evaluated twice. This multiple evaluation can be problematic if foo() affects the value of y directly, or if foo() involves operations that take a long time to compute.

int z = MAX(x, foo(y)) = (x++ < foo(y) ? foo(y): x++)

Because parameters containing side effects are directly expanded in macros, they should be avoided as macro parameters. Equivalent operations should be performed manually in the code before or after macro calls are made.

Self-referential macros

As described in Balancing parentheses, macro expansion substitutes arguments before searching the result and the rest of the file for more macro calls. To prevent infinite recursion during expansion, self-references in macro definitions are not treated as macro calls and are instead passed as-is to the expansion result. This self-referencing can have unexpected results if macros and program variables share names.

Consider the following code sample:

// a global variable called foo
int foo = 8;
…
// a function-like macro, called foo
#define foo 4 + foo

The foo macro expands as follows, leaving a literal string reference to the global integer foo:

foo = 4 + foo
foo = 4 + 8
foo = 12

This effect can cause confusion to anyone reading the code. You should consider the effects of self-referential macros and their expansion when designing macros.

Macro naming

There aren't many restrictions on the names that you can assign to macros. You can use any valid identifier defined in C or C++ as a macro identifier, as long as it's not the preprocessor keyword defined , or any of the C++ named operators. You should check to make sure that macros being added to your code do not redefine existing functions or variables that are present in the code.

During macro expansion, the preprocessor searches the source code looking for instances of the macro identifier. Instances of the identifier that occur in regular code or in certain preprocessor directive statements are replaced with the defined macro value(s). This effect can be seen in the previous self-referential macro example, where an integer variable named foo is substituted into a macro that's also called foo.

You should make sure that macros are assigned unique names that do not conflict with reserved keywords or variable names.

Macros and C++

Several features of C++ can be used to provide the functionality of macros with additional safety. The C++ const keyword allows constants to be declared with type and value information and used in constant expressions, and this information is accessible through a symbolic debugger.

For function-like macros, C++ provides inline function capability that includes type safety and correct handling of arguments that have side effects. This approach can help avoid problems with multiple evaluation.

Last modified: 2014-09-30



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus