23

I read this answer about undefined behaviour, where I saw following statement:

++++++i;     // UB, parsed as (++(++(++i)))

I don't think it is undefined behaviour. I have a doubt, Is it really UB in C++? If yes, then How?

Also, I made program and compiled using g++ prog.cpp -Wall -Wextra -std=gnu++1z -pedantic command, it's working fine without any warning. It's give an expected output.

#include <iostream>
using namespace std;

int main()
{
    int i = 0;
    cout<<++++++i<<endl;
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
msc
  • 33,420
  • 29
  • 119
  • 214
  • 10
    Doesn't look like UB to me. Even if parsed as such. Evaluation should occur outwards. – DeiDei Dec 01 '17 at 20:07
  • 3
    Seems to be well-defined, and equivalent to (a bit pseudo code) `i.operator++().operator++().operator++()`, there is no way to reorder execution of these operators because each next call depends on the return value of previous call. – user7860670 Dec 01 '17 at 20:10
  • 1
    I just tried it out in code with `int main() { int i = 0; ++++++i; std::cout << "i is " << i << "\n"; }` It printed 3, just as expected. – Patrick Avery Dec 01 '17 at 20:12
  • 14
    But the fact that it prints the right number doesn't mean it isn't UB. There might be some interpretations of the language where something else could be printed, or an exception thrown. – Carlos Dec 01 '17 at 20:13
  • 4
    Might have been UB in pre-C++11 era. The follow-up answer mentions the lack of sequence points in C++11 standard thus making the above a well defined behavior. – Ron Dec 01 '17 at 20:13
  • 5
    The most obnoxious constructs often look like UB. Good! – StoryTeller - Unslander Monica Dec 01 '17 at 20:16
  • 1
    The referenced answer specifically mentions C++98 and C++03 standards and may be different in newer standards. – crashmstr Dec 01 '17 at 20:16
  • The follow up to the linked answer (for C++11) specifically mentions `++++i; // Well-defined Behaviour`. – DeiDei Dec 01 '17 at 20:18
  • So post c++ 11 one of the main differences is move semantics and how it deals with temporary variables. Maybe that's a clue? – Carlos Dec 01 '17 at 20:18
  • How do we know that what's undefined isn't simply the bracketing interpretation? If there's only two ++s there's only one way to see it, but if there's 3 it might not be associative? (Or whatever the term is) – Carlos Dec 01 '17 at 20:19
  • @Carlos But this snippet does not involve any temporary variables or move semantics. – user7860670 Dec 01 '17 at 20:21
  • Good point, I was thinking of assignment for some reason. This seems to just be increment x3 – Carlos Dec 01 '17 at 20:22
  • Well, that post mentions that the standard says "Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression." So that is the reason it is undefined - because it is modified more than once by the evaluation of the expression. But it mentions in the followup "Is it true that there are no Sequence Points in C++11? Yes! This is very true." I'm guessing the fact that it is not UB anymore is due to a change in their sequence points. – Patrick Avery Dec 01 '17 at 20:35
  • 3
    @VTT Your inability to imagine a way it could fail doesn't mean it's not UB. A compiler could defer the modifications to `i` until after all the accesses are finished if it wanted to. But even if I couldn't think of a way it could fail, UB is still UB, and a future compiler (or obscure compiler) might be more imaginative than either of us. – David Schwartz Dec 01 '17 at 20:36
  • Well, `++++++i` is equivalent to (I'll use `->`) `++(++(++i))` -> `((i += 1) += 1) += 1` -> `((i = i + 1) += 1) += 1` -> `((i = i + 1) = (i = i + 1) + 1) += 1` -> `((i = i + 1) = (i = i + 1) + 1) = ((i = i + 1) = (i = i + 1) + 1) + 1`, but there are some rules that are slightly different (basically, `expr += 1` is exactly `expr = expr + 1`, but `expr` is only evaluated once). Maybe prior to C++11, the `i`s could be evaluated in a different order? – Justin Dec 01 '17 at 20:38
  • 1
    @Justin You're imagining that what `++i` evaluates to is something something different from `i` itself. It is not. Each of these pre-increments is a pre-increment of the very same object. There is not some temporary returned from `++i` that is incremented by the next operation. It's always `i` that's being incremented -- same object every time. So it's really more like `++i; ++i; ++i;` except there aren't sequence points between the operations. – David Schwartz Dec 01 '17 at 20:40
  • @DavidSchwartz Yeah. I was just looking at cppreference. It says that `++i` is exactly equivalent to `i += 1`, which for built in types is exactly equivalent to `i = i + 1`, except where the `i` is only evaluated once. So idk exactly how it all works.... Expanding the `a += b` into `a = a + b` doesn't seem to always be exactly a copy-paste type of thing – Justin Dec 01 '17 at 20:41

2 Answers2

36

In C++03 it is undefined behavior. In C++11 it is not. There is no sequence point between the various pre-increments. If i was a user-defined type, it would be well-defined behavior because then there would be a function call (a sequence point).

In C++11, the idea of sequence points was replaced with sequenced before/sequenced after. Defect 637 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637) provides an example of a previously undefined construct becoming well-defined (i = ++i + 1).

To understand why it's not undefined behavior, let's look at the pieces we need. ++i is equivalent to i = i + 1 (except i is evaluated only once). Further if we substitute i = i + 1 with inc, ++(i = i + 1) becomes inc = inc + 1.

[expr.ass] states:

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

Thus the assignment in i = i + 1 is sequenced before value computation of inc; however, the assignment in inc = inc + 1 is sequenced after value computation of inc. There is no undefined behavior because the assignments are sequenced.

user9041001
  • 376
  • 2
  • 4
  • so invocation of build-in operators are not sequence points? and built-in operators are not functions? – user7860670 Dec 01 '17 at 20:40
  • @VTT - Before C++11, invocation of built-in operators did not always imply sequencing - and modifying a variable twice in one expression, such as incrementing it twice, therefore always gave undefined behaviour. Built-in operators on basic types (like `int`) do not involve function calls - so it is not possible to change an `operator+()` on basic types (e.g. it is not possible to change addition of `int`s to do subtraction) – Peter Dec 01 '17 at 20:51
  • 1
    There [were a bunch of cases that changed between C++03 and C++11 and became well defined](https://stackoverflow.com/a/23063914/1708801) – Shafik Yaghmour Dec 01 '17 at 20:52
  • @Peter Per [expr.ass]/7, `E1 op= E2` is equivalent to `E1 = E1 op E2` except `E1` is evaluated only once. See rest of my answer on how everything else is sequenced. – user9041001 Dec 01 '17 at 21:07
  • @Peter You see, build-in operators are called *functions* (at least in recent standards) if they were something other than functions in older standards then I would consider it a more radical change than all those sequencing rule changes altogether. – user7860670 Dec 01 '17 at 21:15
  • @VTT - the standard has been historically a bit loose in language. The usage of the word "function" is one of those. A number of core operators (like addition) on basic types have never involved calling a function. However, all operators implement some functionality (e.g. act on values, discard values, produce new values, etc) so can be said to have a function. – Peter Dec 01 '17 at 21:27
9

Sometimes behavior is undefined even though it's hard to imagine how it could be mishandled. But pre-C++11, this was undefined because the same object is modified multiple times without an intervening sequence pointed.

One could imagine a compiler that "optimized" the code by consolidating all the modifications to i. Thus each increment winds up incrementing the original value. But that's not the point. UB is UB if the standard says so. It doesn't matter whether or not we can imagine ways it can fail.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • see [my comment above](https://stackoverflow.com/questions/47600716/confusion-about-undefined-behaviour-in-c#comment82161302_47601116) there were a bunch of things that become well defined in C++11 – Shafik Yaghmour Dec 01 '17 at 20:55
  • 1
    Still, the important point is that something can be undefined behaviour without you having any idea what could go wrong. For example, int i = 1; int j = (++i) + (++i); is undefined behaviour. Someone could argue "it will return either 2 + 3 or 3 + 2, so it's defined to set j to 5, while (++i) - (++i) could return 2 - 3 or 3 - 2, so it's undefined", but it's always undefined. – gnasher729 Dec 02 '17 at 12:54
  • 1
    And "it gives the expected output" is no reason to think it's defined behaviour. Returning what I expect is one of the possible outcomes of undefined behaviour. – gnasher729 Dec 02 '17 at 12:55