-2

If I write like this :

i = i++, i, i++;

It is undefined behaviour in C language.

But, If I write like this:

return i++, i, i++; // Is it UB?

Is it undefined behaviour?

Example:

#include <stdio.h>

int f(int i)
{
    return i++, i, i++; // Is it UB?
}
int main() {
    int i = 1;
    i = i++, i, i++;

    i = f(i);
    printf("%d\n",i);
    return 0;
}
msc
  • 33,420
  • 29
  • 119
  • 214
  • @StoryTeller - `=` binds tighter than `,`, so I believe that's equivalent to `(i = i++), i, i++;`. – Oliver Charlesworth Nov 13 '17 at 12:25
  • @OliverCharlesworth - Yes, the return statement threw me off. It's a red-herring – StoryTeller - Unslander Monica Nov 13 '17 at 12:25
  • @OliverCharlesworth - Though it's the same question still, is `i = i++;` UB? By my previous reasoning, I dare say it's not. http://port70.net/~nsz/c/c11/n1570.html#6.5.16p3 – StoryTeller - Unslander Monica Nov 13 '17 at 12:26
  • @StoryTeller - Consensus seems to be that `i = i++` is UB: https://stackoverflow.com/questions/949433/why-are-these-constructs-using-undefined-behavior – Oliver Charlesworth Nov 13 '17 at 12:27
  • 1
    @OliverCharlesworth - Unwind's answer is 3 years older than C11. How is it UB now given the above wording? There is no *modification* of `i` on the left side. Nothing *to* sequence. The value computation of `i++` is completed before the assignment, according to n1570, anyway. So what's left undefined? – StoryTeller - Unslander Monica Nov 13 '17 at 12:31
  • 1
    @StoryTeller C11 didn't change this, it is still UB. C++11 did however change the rules, I believe. – Lundin Nov 13 '17 at 12:34
  • @StoryTeller Whatever the kind of code don't make sense, let's say it's UB even if it's wrong in C11 ;) – Stargateur Nov 13 '17 at 12:35
  • @StoryTeller - Fair point, it's possible this has changed in C11. I'm going to update my answer to hedge on this (!), but if there is indeed a difference then I agree the answers to that canonical question ought to be updated :) – Oliver Charlesworth Nov 13 '17 at 12:35
  • 1
    @Lundin - Yes, you keep *saying* it's UB. But given the **wording**, I'd like to understand why it's UB even if everything appears to check out. – StoryTeller - Unslander Monica Nov 13 '17 at 12:35
  • @OliverCharlesworth - Don't hurry to update. I'm not sure it isn't UB, or I wouldn't ask. – StoryTeller - Unslander Monica Nov 13 '17 at 12:40
  • @StoryTeller Updated my answer below with sources, for all your language-lawyer needs :) – Lundin Nov 13 '17 at 12:42
  • Since nobody in their right mind would ever use such bad code, I fail to see how it could be useful to future SO users/visitors:( – Martin James Nov 13 '17 at 13:18
  • @MartinJames: In the specific case of `i=(i++,i,i++)` I'd agree, but it would not be implausible to have a function-like macro expand to something like `(thing++,foo(),thing)`, or for such a macro to be invoked a context yielding something `thing=(thing++,foo(),thing);`. IMHO, there's no reason such a thing should be problematic even if `foo()` happens to be a function-like macro rather than a bona fide function call. – supercat Nov 15 '17 at 15:35

2 Answers2

12

i = i++, i, i++; is UB because = has higher precedence than , and so the expression gets parsed as (i = i++), i, i++ where the sub-expression i = i++ invokes UB1).

Even if the code had been written as i = (i++, i, i++); it would still have been UB, because now there is no sequence point between the right-most i++ and i, the left operand of = 1).

However, when you remove the i = part, you remove that ambiguous behavior. return i++, i, i++; must get sequenced as:

i++, the left one
sequence point from left comma
i
sequence point from right comma
i++, the right one
sequence point before returning

So it is well-defined.


Sources:

1) C11 6.5/2

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.

And then also C11 6.5.16/3

The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

Notable, the above text regarding assignment is different between C11 and C++11.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 4
    But very bad code. As a rule of thumb, any code mixing `++` with other operations in the same expression is suspicious. There is rarely ever a reason to mix `++` with other operations, in 99% of the cases it is just sloppy, dangerous style. – Lundin Nov 13 '17 at 12:30
  • Sorry to nitpick, but I think that you'd like to remove "un" in "you remove that unambiguous behavior". – Virgile Nov 13 '17 at 13:32
  • So then `i = (i++, i++, i)` is defined behavior? Ditto `i = i, i++, i++`? – TLW Nov 14 '17 at 04:54
  • @TLW No, in case of `i = (i++, i++, i)`, the two sub-expressions `i` and `(i++, i++, i)` are not sequenced. Meaning that `i` can get evaluated first and then immediately afterwards the left-most `i++`. So it is still in theory UB (in C, would have been ok in C++11). In case of `i = i, i++, i++`, it is well-defined. – Lundin Nov 14 '17 at 07:37
  • @Lundin: I know that ordering isn't always transitive, but `i=(i++,i)` would seem like an odd spot for it to be non-transitive. The Standard recognizes that non-compound assignments may determine *what* lvalue is being modified (e.g. in cases like `p[i]`) before evaluating the right-hand side, but must behave as though they don't read from the left-side lvalue at all, and don't write any part of it until all reads using the same lvalue on the right-hand side have completed. In some case like `long long i;` ... `i=i>>1` or `i*=3` a compiler might be able to write one half of `i`... – supercat Nov 14 '17 at 15:42
  • ...before reading the other, but only if it could prove that wouldn't affect the result. Further, given `i=(i++, foo(), i);` the increment of `i` would be sequenced entirely before `foo()` and the assignment entirely after, and I don't think that would be affected by whether `foo()` did anything with `i`. So it would seem curious for the behavior of `i=(i++, i)` not to be implied by other aspects of the Standard. – supercat Nov 14 '17 at 15:44
  • @supercat Suppose we have a `volatile` data receive/transmit register `REG`. In the hardware spec for this register, they say that the register is cleared once read (this situations do exist in many hardware peripherals). Suppose the task is to receive one byte of data, increase it by one, and send it out again. With code like `REG = (REG++, whatever);`, the program might first evaluate the lvalue, and upon evaluation destroy the contents as per the spec of this hardware. It will then send out an incorrect value as `REG++` is not the received value + 1. – Lundin Nov 14 '17 at 15:58
  • And the rules of "the abstract machine" enforces the program to read the volatile variable twice. Had the program evaluated `REG++` first, then the program would have worked as expected. – Lundin Nov 14 '17 at 15:58
  • @Lundin: The exact semantics of `volatile` objects are to a large extent Implementation Defined; some platforms may define semantics for certain read-modify-write operations that are different from doing a read and then a write. If an implementation doesn't specify anything "interesting" about `++`, I would expect `REG=(REG++,REG);` to read `REG`, write a value one larger than what was read, read the register again, and write that value. The more interesting case would be `*p = (*q+=1,*r`) where all variables are of the same type and none are `volatile` nor `restrict` qualified. – supercat Nov 14 '17 at 16:50
8
return i++, i, i++;

No, it's not undefined behaviour because the comma operator defines a sequence point which separates the modifying accesses to i.

Thus this code is equivalent to:

i++;        // The left-most subexpression
i;
return i++; // The right-most subexpression

For comparison:

i = i++, i, i++;

This is undefined behaviour because , has lower precedence than =, so it's equivalent to:

(i = i++), i, i++;

And i = i++ is undefined behaviour (see this question for more on this particular case).

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680