Using comma operator (a, b) when a is a block of code
Asked Answered
Q

1

4
#include <stdio.h>

int main()
{
    int sum = 0, result = (({for (int i = 0; i < 5; i++) sum += i;}), sum);
    
    printf("result = %d\n", result);

    return 0;
}

displays

result = 10

I'm using whatever the default version of C is in gcc. Is it specific to gcc? I'm aware (a, b) returns b but I didn't expect to get it to work when a was a {} block. Can anyone explain why it works and why the block has to be bracketed to get it to work?

EDIT: This is a contrived simplified example of a #define I was trying out. Some programmer dude's comment (thanks) clears it up. Case closed.

Quicklime answered 9/8, 2023 at 10:26 Comment(4)
Statement-expressions is a GCC-specific extension, it's not part of standard C.Hydromedusa
More generally, the comma expression evaluates the left-hand side of the expression, throws away the result, and then evaluates the right-hand side. In that specific order. The result of the comma expression is result of the the right-hand side only.Hydromedusa
You can easily put the code inside a function and print the value of result() without any fancy compiler extensions.Gordon
int sum = 0, result = (({for (int i =0; i < 5; i++) sum +=i;}), sum);?!?! Where's the eye bleach? Hopefully this is just a contrived example to illustrate your question and not real code written by some misguided person. That code takes readability and maintainability, burns them, flushes the ashes down the toilet, follows the flow to the sewage treatment plant, and nukes the entire sewage treatment plant from orbit just to be sure both readability and maintainability are dead and can never be found again.Topaz
C
5

I'm using whatever the default version of c is in gcc.

Currently that's -std=gnu17, equivalent to "lax ISO C17 with GNU extensions". It is not a conforming mode and not recommended for beginners.

Is it specific to gcc?

Yes, or rather it is specific to the GNU C dialect, which is at least partially supported by clang as well. As mentioned in comments, this is a non-standard extension known as "statement expressions" or "statements in expressions". It allows multiple expressions/lines and optionally returns a value like a function-like macro.

I'm aware (a, b) returns b but I didn't expect to get it to work when a was a {} block

The comma operator has a special evaluation rule guaranteeing that the sub-expression a is fully evaluated before b. Normally this isn't the case with C operators. This means that you can assume that everything at a is executed before b and you may even safely include code using b.

However, a list of variables to be initialized does not come with such a sequencing guarantee. int sum = 0, result=sum; does not guarantee any particular order or sequencing - code such as this is deeply problematic and possibly undefined behavior.


Best practices are to follow standard C whenever possible, to write code as readable as possible and to avoid poorly-defined behavior. Complex "one-liner" code is very bad practice. So this would have been much better written as:

int sum = 0;
for (int i=0; i < 5; i++) 
{
  sum+=i;
}
int result = sum;

Or if you prefer, the 100% equivalent:

int result = 10;
Clearness answered 9/8, 2023 at 10:56 Comment(10)
@EricPostpischil Ah yeah, for some reason I assumed that these were globals. Will fix, thanks.Clearness
However, a list of variables to be initialized does not come with such a sequencing guarantee. int sum = 0, result=sum; does not guarantee any particular order or sequencing - code such as this is deeply problematic and possibly undefined behavior. This is deeply disturbing. Can you substantiate with references to the C Standard or possibly a WG14 document?Mend
@Mend I think the statement is incorrect.Toots
@IanAbbott: I asked a separate question: #76868159 specific to the C language, this behavior seems fully defined in C++.Mend
The evaluation of 0 in int sum = 0, result=sum; is a full expression, so there is a sequence point between evaluation of 0 and evaluation of the next full expression to be evaluated, which is sum in the init-declarator result=sum. The value of sum is not indeterminate because it was initialized explicitly.Toots
@Mend I might remember this wrong, but C only guarantees a sequence point after a so-called "full declarator". In the expression int sum = 0, result=sum;, the (full) declarators are sum and result. And so the sequence points should be like this: sum<sq> = 0, result<sq>=sum;... which is senseless for plain objects/scalar declarators but not for array/aggregate declarators.Clearness
@Lundin: the question was indeed addressed in this answer. Your interpretation is somewhat extreme but not unfounded.Mend
@Mend Looking into it now, there appears to be several shortcomings in the ISO 9899. First of all the infomative Annex C makes various claims about sequence points after full declarators and after initializers, which the actual normative text does not make at all. And then apparently the text defining full declarators was changed in C17 and I cannot find why they changed it.Clearness
@Clearness Annex C was also changed in C17 to remove "the end of a full declarator" from the list of sequence points, except that a "full declarator for a variably modified type" is now also a "full expression", and there is a sequence point between the evaluation of a full expression and the next full expression to be evaluated. (Annex C is not normative, but see C17 6.8p4 & 6.8p5 for the normative text.)Toots
@IanAbbott Err, okay... clearly an improvement... or...? :) Some of these changes just seem random and not taking the big picture in account. I wish they would provide more of a rationale in the actual standard. Instead we end up with yet another "murky waters" section of the language which one best stay clear of when writing code.Clearness

© 2022 - 2024 — McMap. All rights reserved.