Reputation: 392
Considering situations where a comma operator would be of suitable use K&R states (Chapter 3, page 63) "..., and in macros where a multistep computation has to be a single expression."
Now I know that #define SYMVAR (expr1, expr2, expr3)
sets SYMVAR
to expr3
but that's not really the use the book is suggesting. My confusion stems from a lack of proper example for the mentioned use of the comma operator; secondly a multistep computation in a macro would be purposeful only if every comma-separated expression actually contributed in any way to the value of the macro itself which would be possible only if we somehow stored some intermediate values in some temporary variables which doesn't make any sense since we're talking about preprocessor directives.
Am I missing something? What would be a proper example for such use of the comma operator that the book describes?
Upvotes: 0
Views: 1205
Reputation: 17593
Since the value of a comma operator statement is the last comma separated expression in the statement you can use this to perform several expressions while keeping the value only of the last expression.
For instance, lets say that you want to have a macro that will do several calculations but the value of the macro is the last calculation. The following macro takes advantage of the assignment operator being used in an expression to perform a calculation and allow a different calculation to be the result of the macro:
#define SYMVAR(a,b) ( ((a)=(b)), (b)*2)
This could also be a sequence of function calls such as:
int f1(int x) { return x * 3; }
int f2(int x) { return x * 4; }
// call first function f1() and then f2() with the value of the macro being the
// value returned by function f2().
#define SYMVARy(a) (f1(a), f2(a))
An example of using this approach:
#include <stdio.h>
#define SYMVAR(a,b) ( ((a)=(b)), (b)*2)
#define SYMVAR2(a,b,z) ( ((a)=(b)), ((z)=(b))*2)
int main()
{
int n1, n2;
int x1, x2;
int y0;
int i;
for (i = 0; i < 10; i++) {
printf(" i = %d -> ", i);
x1 = SYMVAR(n1, i);
printf(" n1 = %d, i = %d, x1 = %d\n", n1, i, x1);
}
printf("\nloop 2\n");
for (i = 0; i < 10; i++) {
printf(" i = %d -> ", i);
x2 = SYMVAR2(n2, i+3, y0);
printf(" n2 = %d, i = %d, x2 = %d, y0 = %d\n", n2, i, x2, y0);
}
return 0;
}
The output of this program is:
i = 0 -> n1 = 0, i = 0, x1 = 0
i = 1 -> n1 = 1, i = 1, x1 = 2
i = 2 -> n1 = 2, i = 2, x1 = 4
i = 3 -> n1 = 3, i = 3, x1 = 6
i = 4 -> n1 = 4, i = 4, x1 = 8
i = 5 -> n1 = 5, i = 5, x1 = 10
i = 6 -> n1 = 6, i = 6, x1 = 12
i = 7 -> n1 = 7, i = 7, x1 = 14
i = 8 -> n1 = 8, i = 8, x1 = 16
i = 9 -> n1 = 9, i = 9, x1 = 18
loop 2
i = 0 -> n2 = 3, i = 0, x2 = 6, y0 = 3
i = 1 -> n2 = 4, i = 1, x2 = 8, y0 = 4
i = 2 -> n2 = 5, i = 2, x2 = 10, y0 = 5
i = 3 -> n2 = 6, i = 3, x2 = 12, y0 = 6
i = 4 -> n2 = 7, i = 4, x2 = 14, y0 = 7
i = 5 -> n2 = 8, i = 5, x2 = 16, y0 = 8
i = 6 -> n2 = 9, i = 6, x2 = 18, y0 = 9
i = 7 -> n2 = 10, i = 7, x2 = 20, y0 = 10
i = 8 -> n2 = 11, i = 8, x2 = 22, y0 = 11
i = 9 -> n2 = 12, i = 9, x2 = 24, y0 = 12
A Limitation of this approach
One limitation with this approach is the inability of creating temporary variables whose scope is the comma expression itself.
A work around is to use the do { … } while(0)
construct however you then have the problem of the macro is no longer valid in an assignment.
A Few Other Considerations
This sort of thing can be a bit tricky to use. You must always remember that the C Preprocessor is doing text substitution and there is not a great deal of smarts about it.
You should use parenthesis to enforce a specific order of evaluation and to ensure that the substitution text provided when a macro is used will result in the correct order of operations ((i + 3) * 2
and not i + 3 * 2
which are two different statements due to the order of precedence of operators).
Also be aware that using an expression with side effects in a macro that uses a macro argument more than once can result in unintended consequences. For instance:
#define SYMVAR(a,b) ( ((a)=(b)), (b)*2)
int xA, xB = 2, xVal;
xVal = SYMVAR(xA, xB++); // macro argument has a side effect, post increment
would result in xVal
having a value of 6 (xB
incremented once, since post increment, and then multiplied by 2), xA
having a value of 2 (original value of xB
since using post increment) and xB
a value of 4 (xB
is incremented twice).
You could use one of the above macros in something like:
xx SYMVAR(n1, n2);
which would compile with a warning and then fail during the link. With Visual Studio 2017 I see the following warnings and errors when I add the above statement to the above program. Notice these warnings and errors are all about a missing external function xx
and are not compiler errors which you might expect.
Severity Code Description Project File Line Suppression State
Warning C4013 'xx' undefined; assuming extern returning int console_scan d:\users\rickc\documents\vs2017repos\console_scan\console_scan\source_macro.c 27
Error LNK2019 unresolved external symbol _xx referenced in function _main console_scan D:\Users\rickc\Documents\vs2017repos\console_scan\console_scan\Source_Macro.obj 1
Error LNK1120 1 unresolved externals console_scan D:\Users\rickc\Documents\vs2017repos\console_scan\Debug\console_scan.exe 1
Upvotes: 2
Reputation: 31389
melpomene provided an example. Another example, although you could argue whether it's good or bad, is if you want to use the macro in a loop header.
#define MACRO(X) (X--, X>0)
int x=5;
while(MACRO(x)) {
// Do stuff
}
This example is definitely not the best in the world, but the point is that it would not work if you used semicolons instead to separate the expressions, no matter how you encapsulate them.
Upvotes: 1
Reputation: 85767
Imagine a function
void debug_log(const char *s);
that writes the specified string (together with a timestamp) to some sort of log file.
Then you could define the following macro:
#define TWICE(X) (debug_log("Calling TWICE(" #X ")"), (X) * 2)
TWICE(21)
would then evaluate to 42
, but also write a message of the form
[2019-09-03 12:34:56] Calling TWICE(21)
to the log file.
The point is that expressions in C can have side effects. They can modify variables, they can write to files, etc., even if their return value is not used.
Upvotes: 4