Comma operator?! Isn’t that thing just a separator? Nope. It’s occasionally an operator. And it can do this:

int i = (0, 1, 2, 3, 4, 5);

Know what that does? It evaluates the constants 0 through 4, discards the results, and then evaluates and returns the constant 5, which is assigned to ‘i’. Why is that useful? It’s not. The evaluation of the constants is probably optimized away by most compilers. But, the comma introduces a sequence point, so you can do disgusting things like this, ensuring the order of evaluation:

int i = (a2d_init(), a2d_fetchvalue());

I don’t care how expressive you are—don’t ever do that. Just know that you CAN do it and that doing it makes you a bad person.

Other, more kosher, but still questionable uses include loops:

for (i = 0, j = 256; i < 256; i++, j--) {}

That is all. I had personally completely forgotten about this infrequently-used operator until I encountered some strange code recently:

#include <stdio.h>
struct S0 {
  unsigned f1 : 1;
};
 
struct S0 s;
 
int main (void) {
  int x = -3;
  int y = x >= (0, s.f1); /* Ooh! Here it is! */
  printf ("%d\n", y);
  return 0;
}

It’s a reduced test case for compilers, so no—it’s not supposed to make any sense to you. If you’re curious though, you can read more about it here.


Well, hello there HN, reddit, and DZone. I didn’t realize language pedantry was so universally piquing. If you’re interested in embedded development and crave diversity, note that we’re hiring for positions in San Francisco.

This entry was posted in MindTribe Tech and tagged , , . Bookmark the permalink.

26 Responses to Forgotten C: The comma operator

  1. Here is use for the comma operator I saw recently

    • RoelErick says:

      That’s not good! And even a bit silly, if you’ll forgive me.
      It says

      if (!dry_run && ((stdout_closed = true), close_stream (stdout) != 0))

      that’s equivalent to

      if (dry_run, stdout_closed, close_stream (stdout))

      because in C, [expressions evaluating to] integers are automatically converted to boolean where necessary.
      Also, dry_run and stdout_closed are presumably variables (if they were function calls they would be written dry_run() and stdout_closed()). That means their value is retrieved an then discarded. So, the line is equivalent to:

      if (close_stream (stdout))

      In a, b, ...,y, z only the side effects of the expressions a...y matter, their values do not. For z, both its side effect and its value matter.

      • Anonymous Cowherd says:

        if (!dry_run && ((stdout_closed = true), close_stream (stdout) != 0))

        that’s equivalent to

        if (dry_run, stdout_closed, close_stream (stdout))

        Completely incorrect. The && operator in C is not equivalent to the comma operator. Also, you seem to have misread = as ==. The original code is actually equivalent to

        if (!dry_run && (stdout_closed = true) && close_stream (stdout)) { ... }

        However, it’s still most likely buggy. The more logical way to write the code would be

        if (!dry_run && close_stream (stdout)) {
          stdout_closed = true;  /* NOW stdout is closed! Not before! */
          ...
        } else {
          /* Error! close_stream() FAILED! We need to do something here. */
        }
        • Richard says:

          You also missed the point that the && operator behaves in a short circuit manner…
          So if dry_run is true the rest of the statement will not even get executed…

  2. Ricardo says:

    Same goes for Javascript. UglifyJS uses it to achieve better minifying.

  3. Peter says:

    int i = (a2d_init(), a2d_fetchvalue());

    Why is this so bad? I personally don't use such construct, but it nicely suggest that before fetching value and assign it to i, one needs to call init() first. Looks ok to me.

  4. AJackson says:

    I use it all the time in the assignment operator where you return a reference to the object in question.
    here is a small example (normally they are a bit more verbose).
    class A
    {
    public:
    A& operator=(const A& rhs) { return v = rhs.v, *this; }
    private:
    int v;
    };

    • Anonymous Cowherd says:

      That’s just a slightly more convoluted way of writing

      A& operator=(const A& rhs) { v = rhs.v; return *this; }
      
  5. Jamie says:

    Along those lines, this syntax is valid in C and Javascript:

    if ((longvariablename=j+10)>k) {

    }

    meaning

    longvariablename=j+10;
    if (longvariablename>k) {

    }

    I use “longvariablename” simply to show that it does have a purpose in Javascript (to shorten), but I think it would be hard to argue any benefit in a compiled language. It is certainly confusing semantically. But shorter.

  6. Elan Hasson says:

    I had encountered the same exact snippet of code on “Embedded in Academia”.

  7. I confess freely to use and abuse of the comma operator. It is in the language for a reason, and the reason is precisely to do the kinds of things you describe. I regularly do this:

    while (token = scanner.next(), token.size())
    {
    … blah blah blah …
    }

    It’s neat. It’s elegant. If there is some confusion or I don’t think a reader will see it, I reformat it slightly:

    while (token = scanner.next(),
    token.size())
    {
    … blah blah blah …
    }

    I

  8. Tom says:

    For those asking, the various examples are bad because you can do exactly the same thing more readably with two statements. In particular this code:

    A& operator=(const A& rhs) { return v = rhs.v, *this; }

    is exactly equivalent to:

    A& operator=(const A& rhs) { v = rhs.v; return *this; }

    What is the point of using the comma operator to combine the two statements into one here? The only case where it is (sometimes) better is where syntactically only one statement is allowed, such as the for-loop example. Another relatively-common variant on this is in nested loops where each item processed in the inner loop also deals with one item in the outer loop:

    std::vector x;
    std::vector y; // Note – x.size() is an integer multiple of y.size()

    for( std::vector::iterator i = x.begin(); i != x.end(); i++) {
    for(std::vector::iterator j = y.begin(); j != y.end(), i != y.end; j++, i++) {
    // Do something with i, j
    }
    }

    • Tony Arkles says:

      Tom, I think you have a bug :)

      Did you mean this?

      std::vector x;
      std::vector y; // Note – x.size() is an integer multiple of y.size()

      for( std::vector::iterator i = x.begin(); i != x.end(); i++) {
      for(std::vector::iterator j = y.begin(); j != y.end() && i != y.end; j++, i++) {
      // (Note the loop condition is now an && operator instead of a ,)
      // Do something with i, j
      }
      }

  9. Pingback: The comma operator in C | A rail in the sky

  10. David Dillon says:

    I use it for situations where you need something along these lines, though I don’t enjoy the hit in readability:


    x.foo(y) ; // need to do something before the loop
    while(y.bar()) {
    ...
    x.foo(y) ; // and for every iteration
    }

    becomes:


    while(x.foo(y), y.bar()) {
    ...
    }

  11. Angus Croll says:

    I wrote about it here with a gazillion slightly useful examples (for JavaScript)

    http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/

  12. Dan Sutton says:

    I love it! “disgusting things like this”: made me roar with laughter — it’s amazing how many programmers will find more and more stupid and arcane ways to make code unreadable. Great little article!

  13. Hell what does that do??? I know about the second use of it. The first use of it is a bit intriguing to me. :D anyways great article!

  14. djackson says:

    I see it pop up in for loops ever now and then; it’s not so bad. Using it in a variable initialization wouldn’t fly here; I don’t dig that.

  15. George says:

    The comma is one of the few operators in Ansi C (together with &&, || and ?:) for which the temporal order of evaluation of operands is guarranteed (for comma, first the left, then the right one).
    Note that order of evaluation is different from operator associativity.

  16. spinorkit says:

    If you want to see how overloading the comma operator in C++ can make code more readable, have a look at the Blitz++ template library for scientific computing. E.g. it is used here to facilitate initializing arrays:
    http://www.oonumerics.org/blitz/manual/Frames.html

  17. Prinz R. says:

    I use the comma operator. But where is it ?

    	while 
    	(
    	                     cProductEntry = ProductEntry::ProductEntry(),	// Initialise
    		ProductEntry(hElem, cProductEntry, iProductEntryIndex)
    	)
    	{
    		cProductConfigs.push_back(cProductEntry);
    		iProductEntryIndex++;
    	}
  18. Pingback: Всем сотрудникам отдела! «

  19. Paul Bruner says:

    Thanks! I have been looking for how this thing works. If you want something really funky looking check out the Lua source code sometimes.


    #define luaL_addchar(B,c) \
    ((void)((B)->n size || luaL_prepbuffsize((B), 1)), \
    ((B)->b[(B)->n++] = (c)))

    It took a bit for me to figure it out but here is the order it runs in (Note: void is just there to avoid compiler errors)
    1: “((B)->n size” if the number of chars is less than the buffer size
    2: “||” Most compilers will then skip the rest of a conditional unless the left is false so then
    3: “luaL_prepbuffsize((B), 1)” it runs this witch expands the buffer
    4: ” ((B)->b[(B)->n++] = (c)” then we run this after the comma.

    Your right, I would never use this kind of code within a function. As a define it makes it a quick, efficient putc.

Leave a Reply