General Properties of Macros in C
So far we have studied simple macros, parametric macros and operators for macros in C language.
Now that we know how to define and use macros, let's see some general properties that apply to all macros in C language.
In this lesson we will see how macros can invoke other macros, exceptions to macro expansion, the behavior of macros with respect to scope and the #undef directive.
Properties of Macros
We have studied, in previous lessons, how to define simple macros and parametric macros in C language. We have seen their syntax and usage. Now we focus on some properties that generally apply to both cases.
Nested macro invocations
One of the most powerful features of macros in C language is the ability to invoke other macros within the body of a macro. This allows you to create complex macros and compose different functionalities into a single macro.
Let's take an example. We define, with a simple macro, the constant
#define PI 3.14159265359
We can reuse the PI macro within other macros. We can, for example, create a parametric macro for calculating the area of a circle and another for calculating its perimeter:
#define CIRCLE_AREA(r) (PI * r * r)
#define CIRCLE_PERIMETER(r) (2 * PI * r)
In both parametric macros we have invoked, in the body itself, the PI macro.
To understand how the preprocessor works in these cases, let's suppose we feed it a code excerpt like this:
double radius = 2.0;
double area = CIRCLE_AREA(radius);
double perimeter = CIRCLE_PERIMETER(radius);
When the preprocessor analyzes this code, it first replaces, in place of the macros, the body of the macros themselves. In this case, the preprocessor will replace CIRCLE_AREA(radius) with PI * radius * radius and CIRCLE_PERIMETER(radius) with 2 * PI * radius:
double radius = 2.0;
double area = PI * radius * radius;
double perimeter = 2 * PI * radius;
At this point, the preprocessor analyzes the code a second time and replaces, in place of the PI macro, the numeric value 3.14159265359:
double radius = 2.0;
double area = 3.14159265359 * radius * radius;
double perimeter = 2 * 3.14159265359 * radius;
This procedure is repeated until the preprocessor no longer encounters any macros to replace.
In summary:
Nested Macro Invocations
In C language, the body of a macro can contain invocations to other macros.
There is, however, a small exception to this rule. That is, a macro cannot invoke itself within its own body.
Let's clarify with an example. Suppose we want to create a macro for calculating the factorial of an integer. Let's try to implement it, even though it is not allowed, in a recursive manner. We define the FACT macro:
/* INCORRECT CODE */
#define FACT(n) ((n) == 0 ? 1 : (n) * FACT(n - 1))
Unlike a function, a macro is not invoked but expanded, its body, that is, is replaced in place of the macro itself. Trying to compile the following code:
int factorial_of_3 = FACT(3);
what happens is that the replacement would proceed infinitely. In fact, the preprocessor would replace FACT(3) with:
int factorial_of_3 = ((3) == 0 ? 1 : (3) * FACT(3 - 1));
Then, subsequently, it would replace FACT(3 - 1) with:
int factorial_of_3 = ((3) == 0 ? 1 : (3) * ((3 - 1) == 0 ? 1 : (3 - 1) * FACT(3 - 1 - 1)));
Again, it would replace FACT(3 - 1 - 1) with:
int factorial_of_3 = ((3) == 0 ? 1 : (3) * ((3 - 1) == 0 ? 1 : (3 - 1) * (((3 - 1 - 1) == 0 ? 1 : (3 - 1 - 1) * FACT(3 - 1 - 1 - 1))));
And so on, infinitely. This behavior is an error and the preprocessor, to avoid getting into an infinite loop, does not allow a macro to invoke itself.
In particular, according to the C standard, if the preprocessor encounters the name of the macro itself in the body of a macro, it will never replace it and leaves it intact. Therefore, the above code simply becomes:
int factorial_of_3 = ((3) == 0 ? 1 : (3) * FACT(3 - 1));
The invocation FACT(3 - 1) will not be replaced and, obviously, the program will not be compiled since there is no function called FACT.
In summary:
A Macro Cannot Invoke Itself
The C language does not allow the definition of recursive macros, that is, macros that invoke themselves.
In the case where the preprocessor encounters a macro that invokes itself in its own body, the internal invocation will not be replaced and the macro will be left intact.
Exceptions to Macro Expansion
We have seen that the preprocessor replaces macros with their body, through the process called expansion.
There are exceptions to this mechanism. To clarify better, let's consider the following example:
#define SIZE 1024
int BUFFER_SIZE;
printf("Enter the value of BUFFER_SIZE\n");
scanf("%d", &BUFFER_SIZE);
if (BUFFER_SIZE > SIZE) {
printf("ERROR: Maximum SIZE exceeded");
}
In this example we have defined a simple macro, SIZE, which contains the maximum size we can assign to a buffer.
When the preprocessor examines the above code, it happens that:
-
when it encounters the identifier
BUFFER_SIZEit will not replaceSIZEwith 1024, sinceSIZEis part of the identifierBUFFER_SIZEitself;In other words, the preprocessor does not replace the name of a macro if it is part of an identifier. Therefore the line:
int BUFFER_SIZE;will remain unchanged.
-
when it encounters the identifier
SIZEwithin theifcondition, instead, it will replaceSIZEwith 1024:if (BUFFER_SIZE > 1024) { printf("ERROR: Maximum SIZE exceeded"); } -
Furthermore, when it encounters the token
SIZEwithin the string"ERROR: Maximum SIZE exceeded", it will not replaceSIZEwith 1024, sinceSIZEis part of a string literal.In other words, the preprocessor does not replace the name of a macro if it is part of a string literal.
In summary:
Exceptions to Macro Expansion
The preprocessor does not replace the name of a macro if it:
- is part of the name of an identifier;
- is contained in a string literal.
We must, however, make a small observation. We said that the name of a macro is not replaced if it is part of an identifier. However, if the name of a macro is equal to the entire name of an identifier, and therefore is not a part of it, then the replacement occurs normally.
For example:
#define NAME test
int NAME = 5;
int MY_NAME = 6;
In this case, the preprocessor will replace NAME with test in the first line, but not in the second, since the name of the macro NAME is a part but not the whole of the identifier MY_NAME. Therefore the above code becomes:
int test = 5;
int MY_NAME = 6;
Macros and Scope
The definition of a macro does not follow the normal visibility rules that apply, instead, to variables.
First of all, the definition of a macro is always global. Let's see an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
In this example we have created a function circle_area that calculates, as the name says, the area of a circle. Within the function we have defined the macro PI which represents the value of
Unlike a local variable, the PI macro is global and visible throughout the source file. Therefore, we can use it also within the main function. In fact, in lines 13 and 14, we have used the PI macro to print the value of
Furthermore, a macro is visible only from the point where it is defined until the end of the source file. If we try to modify the above source code in this way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
We get a compilation error. In fact, we have used the PI macro within the print_circle_area function, but the PI macro is defined after the function itself. Therefore, the preprocessor is not able to replace PI with the numeric value of
Trying to compile with gcc we get an error like this:
test_macro.c: In function 'print_circle_area':
test_macro.c:5:13: error: 'PI' undeclared (first use in this function)
5 | PI, radius, radius);
| ^~
Finally, the definition of a macro has effect only in the file where it is defined. We will study this behavior in detail when we talk about programs composed of multiple source files. For the moment, let's remember that a macro is global and visible throughout the source file where it is defined.
In summary:
Macros and Scope
The definition of a macro has effect, that is, is visible and usable, according to the following rules:
-
A macro is always global for the source file where it is defined;
Macros defined within code blocks or functions are globally visible within the file itself;
-
The visibility of a macro is valid from the point where it is defined until the end of the source file;
A macro cannot be used at a point preceding its definition.
Directive #undef
Normally, the preprocessor does not allow redefining a macro with the same name.
For example, the following code is not valid:
#define PI 3.14159265359
#define AREA(r) PI * r * r
printf("Area of a circle with radius 2: %f\n", AREA(2));
/* ERROR: Redefinition of the AREA macro */
#define AREA(r) r * r
printf("Area of a square with side 2: %f\n", AREA(2));
In the above example, we first defined the AREA macro to calculate the area of a circle. Subsequently, we attempted to redefine the AREA macro to calculate the area of a square.
This behavior is not allowed and the preprocessor will return a compilation error.
You can redefine a macro only if the new definition is identical to the previous one except for spaces. For example, the following code is correct:
#define PI 3.14159265359
#define AREA(r) PI * r * r
printf("Area of a circle with radius 2: %f\n", AREA(2));
/* OK: Redefinition of the AREA macro */
#define AREA(r) PI*r*r
Obviously, in this case, we cannot use AREA to calculate the area of a square.
There is, however, a preprocessor directive, which we have not yet studied, that allows you to deactivate the definition of a macro. This directive is #undef.
Using the #undef directive, we can deactivate the definition of a macro and, subsequently, redefine it with a new body. Returning to the previous example, we can write the code this way:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
In this way, we deactivate the AREA macro with the #undef directive and we can redefine it with a new body.
In summary:
Directive #undef
The #undef directive allows you to deactivate the definition of a macro in C language.
The syntax is:
#undef MACRO_NAME
A macro, therefore, will be valid and visible from the point where it was defined until the #undef directive or until the end of the source file.
In Summary
In this lesson we have seen some general properties of macros in C language:
- We have seen that a macro can invoke other macros within its own body, but cannot invoke itself;
- We have studied some exceptions to macro expansion, that is, cases in which the preprocessor does not replace the name of a macro; in particular, if the name of a macro is part of an identifier or a string literal;
- We have analyzed the behavior of macros with respect to scope and we have seen that a macro is global and visible throughout the source file where it is defined;
- Finally, we have studied the
#undefdirective that allows you to deactivate the definition of a macro.
In the next lesson, we will analyze some details on the syntax of the C language and macros that in previous lessons we have only mentioned.