0

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?

6
  • 1
    It is not true that “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”. Expressions have both values and side effects. Side effects include such things as changing values in other objects (assigning to variables as you note, but not necessarily temporary variables), writing to standard output or to files, and more. Commented Sep 3, 2019 at 19:22
  • 1
    Temporary variables could be passed to the macro as well: #define MULTI_STEP(t, x, y) (t = x + y, t * t + 2) (or simply assumed to be declared) Commented Sep 3, 2019 at 19:25
  • 1
    We saw a getch or similar macro recently that checked whether a buffer contained characters, issued a read operation to get more characters if it did not, and then evaluated to the value of the next character in the buffer (or EOF). This macro needs to be a single expression, as that is how getch() is used. Just generally, we might implement it as (UpdateBuffer(), Buffer.Next < Buffer.Length ? EOF : Buffer.Data[Buffer.Next++]). So this expression uses the comma operator to evaluate a function then provide a value. Commented Sep 3, 2019 at 19:27
  • 2
    Your example does not do what you say – SYMVAR is expanded to (expr1, expr2, expr3). You might be confusing it with an assigment. x = SYMVAR; would set x to the value of expr3, after first evaluating the first two expressions (including side effects), and that sounds like the example you’re looking for. Commented Sep 3, 2019 at 19:48
  • 1
    "#define SYMVAR (expr1, expr2, expr3) sets SYMVAR to expr3" it does not. But the preprocessor replaced every occurrence X by (expr1, expr2, expr3) Commented Sep 4, 2019 at 9:56

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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 

Comments

1

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.