Puzzle 5

Let’s talk about the comma operator , in C. You’ve seen in plenty of times already:

In variable initialization

int a = 5, b = 6;

and in for loops.

for (int i = 0, j = 3; i < 5 && j < 5; i++, j++)

But do you really know what’s going on? Haven’t you been the least bit curious? This class is a “death to abstraction” after all, so let’s clear up this bit of haze surrounding this comma operator as well. While there is no overloading in C, the comma is actually overloaded to be both an operator and a separator. In the above two examples, it is serving as a separator between multiple expressions. The case of being a separator is not so interesting. Let’s look at the operator example instead.

What is the output of the following program comma.c?

#include <stdio.h>
int main() {
    int a = 1, 2, 3;
    printf("a : %d\n", a);
    return 0;
}

Trick question! Doesn’t compile. Oops. What about this?

Here is comma1.c:

#include <stdio.h>
int main() {
    int a;
    a = 1, 2, 3;
    printf("a : %d\n", a);
    return 0;
}

And comma2.c:

#include <stdio.h>
int main() {
    int a;
    a = (1, 2, 3);
    printf("a : %d\n", a);
    return 0;
}

So what’s the output? Go on, take a case – don’t just lazily scroll down. Guess!

The answer is that comma1.c outputs 1 and comma2.c outputs 3. So what’s going on? As it turns out, the comma ( , ) has lower precedence than the assignment operator ( = ). This means that in comma1.c, a gets assigned to 1, and only afterwards the other two expressions are evaluated. If you compile this code with -Wall, it will show you the following warning:

warning: expression result unused [-Wunused-value]

The other two expressions in the line simply return 2 and 3, but since the return values are not assigned to anything, the compiler just disregards them.

In comma2.c, however, the ( ) operator has higher precedence than the assignment operator. So in this case, the final return value is the result of the grouped expression; thus, 3 is returned to be assigned to a, and the return values 1 and 2 are ignored. A similar warning is generated with this code as well.

Remember that expressions are evaluated left to right and the return value is that of the rightmost expression.

With this in mind, the following statement makes more sense all of a sudden.

int array[] = {23,34,12,17,204,99,16};

The braces { } group statements together. The comma operator returns a value, and the braces group these comma statements together, and the result is a new statement that groups the statements within together; we call such a grouping an array.

Let’s return to the second case, where the comma can be used as a separator for expressions as well. This can be a particularly useful way of grouping together multiple short, concise statements.

int j = (func1(), func2());

Or for a more context-filled example:

int main() {
    int x = 10, y;
  
    // The following is equavalent to y = x++
    y = (x++, printf("x = %d\n", x), ++x, printf("x = %d\n", x), x++);
  
    // Note that last expression is evaluated
    // but side effect is not updated to y
    printf("y = %d\n", y);
    printf("x = %d\n", x);
  
    return 0;
}

I hope this puzzle clears up some of the mystery with the comma operator!

Here are some further resources to understand commas.