Details on Macros and Syntax in C
In this lesson we will address some details on the syntax and operation of macros that we deliberately omitted in previous lessons.
In particular, we will focus on how to avoid evaluation and precedence errors, and on how to create macros that combine multiple expressions or multiple statements.
Macros and Parentheses
In previous lessons we defined parametric and non-parametric macros using, within their body, an amount of parentheses seemingly excessive at first glance.
For example, when we defined the MAX macro, in the lesson on parametric macros, we wrote:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
In this case, we both enclosed the parameters in parentheses whenever we used them, and enclosed the entire expression in parentheses.
The question naturally arises at this point: are all these parentheses necessary? The answer is positive as we will see now.
In general, when writing the definition of a macro, two rules should be followed.
The first rule is: if the body of a macro contains one of the parameters, it is always better to enclose it in parentheses. This is because, if you don't do this, you risk obtaining unexpected results.
Let's take an example. Suppose we want to implement a macro, SQUARE, that calculates the square of a number. The definition could be the following:
#define SQUARE(x) x * x
However, a definition written this way hides a pitfall. In fact, if we use the SQUARE macro in this way:
int value = 5;
/* Let's calculate the square of value + 1 */
int result = SQUARE(value + 1);
we would expect that, using the SQUARE macro, the value of result is 36. That is, that the macro calculates the square of 6. Actually it is not so.
The value of result will be, instead, 11! This is because the SQUARE macro has been expanded to:
int result = value + 1 * value + 1;
Therefore the expression will be evaluated as 5 + 1 * 5 + 1, which is equal to 11, since the multiplication operator * has a higher precedence than the addition operator +.
To solve this problem, we must enclose the parameters in parentheses. The correct definition of the SQUARE macro is:
#define SQUARE(x) (x) * (x)
By doing so, during the macro expansion phase, the code will become:
int result = (value + 1) * (value + 1);
and the value of result will be 36, as we expected.
The second rule to respect, to avoid unpleasant surprises, is: if the body of a macro contains one or more operators, it is always better to enclose the entire body in parentheses.
Let's return to the previous example of the SQUARE macro. Suppose we want to calculate, using SQUARE, the reciprocal of the square of a number:
We could write the code in this way:
#define SQUARE(x) (x) * (x)
double x;
printf("Insert the value of x:\n");
scanf("%lf", &x);
double r = 1 / SQUARE(x);
The problem is that, with this code, we will never get what we expect. In fact, the SQUARE macro will be expanded to:
double r = 1 / x * x;
However, since the division operator / has the same precedence as the multiplication operator *, the expression will be evaluated as:
Therefore the value of r will always be 1!!!
The correct version of the above code is obtained by enclosing the entire body of the macro in parentheses:
#define SQUARE(x) ((x) * (x))
By doing so, the expression will be evaluated as:
double r = 1 / ((x) * (x));
and the value of r will be the reciprocal of the square of x, as we expected.
In general, therefore, it is always better to be prudent and enclose both the parameters and the entire body of the macro in parentheses. Summarizing:
Rules for macro definition
When writing a macro, parametric or not, in C language, it is always better to follow the following rules to avoid hidden errors and unwanted effects:
- Enclose parameters in parentheses: if the body of a macro contains one of the parameters, it is always better to enclose it in parentheses to avoid evaluation errors.
- Enclose the entire body in parentheses: if the body of a macro contains one or more operators, it is always better to enclose the entire body in parentheses to avoid precedence errors.
Macros and Multiple Statements
A very interesting use of macros is the combination of multiple statements. This means that, within the body of a macro, we can write more than one statement, separated by the comma operator.
For example, suppose we want to create a macro, called PROMPT_DOUBLE, that shows a message to the user and reads a double value from the keyboard. The definition could be the following:
#define PROMPT_DOUBLE(variable) \
(printf("Insert " #variable ": "), scanf("%lf", &variable))
In this macro, both the call to printf and the call to scanf are two expressions, so it is possible to combine them with the comma operator.
We can invoke the PROMPT_DOUBLE macro in this way:
double x;
PROMPT_DOUBLE(x);
Note that we have enclosed the body of the macro in round parentheses parentheses. We could have used curly braces, but in this case we would have problems.
In fact, if we wrote the macro in this way:
#define PROMPT_DOUBLE(variable) { \
printf("Insert " #variable ": "); \
scanf("%lf", &variable); \
}
And then we used it in this way:
double x;
if (condition)
PROMPT_DOUBLE(x);
else
x = 0;
We would get a compilation error, since the PROMPT_DOUBLE macro would be expanded to:
1 2 3 4 5 6 7 8 9 | |
In particular, line 7 would cause an error. In fact, the compiler would treat the single semicolon as an empty statement, concluding the if statement. The else clause would not belong to any if, generating an error.
We could have solved the problem simply by omitting the semicolon after the macro in this way:
double x;
if (condition)
PROMPT_DOUBLE(x)
else
x = 0;
But, by doing so, our program would appear strange and we would not maintain consistency between statements.
Summarizing:
Macros as combination of multiple expressions
When writing a macro in C language, it is possible to combine multiple expressions within the body of the macro. To do this, it is sufficient to separate the expressions with the comma operator.
In this case, it is always better to enclose the body of the macro in round parentheses, to avoid compilation errors.
The general syntax of a macro with multiple expressions is:
#define MACRO_NAME(parameters) (expression1, expression2, ..., expressionN)
What has been said so far applies if we want to combine multiple expressions. It does not apply, however, if we want to combine multiple statements. In this case the comma operator would serve little purpose, since it is not able to combine statements.
To solve the problem, in this case, we can exploit a trick. We can, that is, combine the statements within a do-while loop that is executed only once. Let's see how to do it with an example.
We want to write a modified version of the PROMPT_DOUBLE macro that, in addition to asking the user to insert a double value, checks that the inserted value is greater than zero. If it is not, the absolute value of the value is saved.
We can write the macro in this way:
#define PROMPT_POSITIVE_DOUBLE(variable) \
do { \
printf("Insert " #variable ": "); \
scanf("%lf", &variable); \
if (variable <= 0) \
variable = -variable; \
} while (0)
Let's make some observations. First of all, we have enclosed the set of statements in the body of the do-while loop. This allows us to write more than one statement within the macro.
As the condition of the loop, we chose 0. This is because the do-while loop is executed at least once, regardless of the condition. In this case, the condition is always false, so the loop is executed only once. Therefore, our statements will be executed only once.
Finally, we note that we have omitted the semicolon at the end of the loop. In this way, when we use the macro, we can insert the semicolon as if it were a single statement.
We can use the PROMPT_POSITIVE_DOUBLE macro in this way:
double x;
PROMPT_POSITIVE_DOUBLE(x);
After the expansion of the macro, the code will become:
double x;
do {
printf("Insert " "x" ": ");
scanf("%lf", &x);
if (x <= 0)
x = -x;
} while (0);
In this way, our code will work correctly and will maintain consistency between statements.
Summarizing:
Macros as combination of multiple statements
When you want to create a macro as a combination of multiple statements in C language, you cannot use the comma operator.
You can, however, exploit a do-while loop that is executed only once, that is with the condition always false.
Therefore, the general syntax of a macro with multiple statements is:
#define MACRO_NAME(parameters) \
do { \
statement1; \
statement2; \
... \
statementN; \
} while (0)
When you want to use it in the code, you can use the following syntax:
MACRO_NAME(parameters);
In Summary
This lesson was useful for understanding how to write macros avoiding evaluation and precedence errors.
In particular, we found two empirical rules:
- Enclose parameters in parentheses: if the body of a macro contains one of the parameters, it is always better to enclose it in parentheses to avoid evaluation errors.
- Enclose the entire body in parentheses: if the body of a macro contains one or more operators, it is always better to enclose the entire body in parentheses to avoid precedence errors.
We have also seen how to create macros that combine multiple expressions or multiple statements, exploiting the comma operator or a do-while loop that is executed only once.
In the next lesson we will see the predefined macros, that is macros that the compiler defines a priori and that we can use to obtain information about the source code.