0

I am not able to understand prefix operator behaviour in scenario when it is used multiple times in a statement .
Here is an example code to illustrate my problem

#include<iostream>
using namespace std;

int main()
{   int a=2,adup=2;
    int b = (++a) * (++a);
    cout<<endl<<"square of "<<a<<" is "<<b<<endl;//i get 16
    int c = (++adup) * (++adup) * (++adup);
    cout<<endl<<"adup is "<<adup<<" and c is "<<c<<endl;// i get 80
    return 0;
}

Save above file as code.cpp and run 'gcc code.cpp -lstdc++' to verify.
It seems that in first case 2 was incremented twice then b = 4x4
But in second case I was hoping 5x5x5 on the lines of above but we ge 4x4x5 . How can that be explained ?
Thanks.

BTW , I found that postfix operator seems logical . it simply updates value and uses it next time variable is used even when in same statement .

After reading answers my conclusion is - Can it be said that using pre or post operation on same variable more than once in a statement will be undefined behaviour regardless of anything on LHS of expression ?

user1371666
  • 445
  • 1
  • 5
  • 18
  • might be a case of undefined behavior https://stackoverflow.com/questions/949433/why-are-these-constructs-using-pre-and-post-increment-undefined-behavior – jspcal Apr 16 '22 at 04:34
  • 1
    A note about terminology: `++a` is called a _prefix increment_, and `a++` is called a _postfix increment_. – heap underrun Apr 16 '22 at 04:36
  • You only use the prefix increment? – Sebastian Apr 16 '22 at 04:36
  • from what i know , undefined behaviour is when we try to update variable using prefix or postfix and assign updated value to variable itself . isn't that true ? – user1371666 Apr 16 '22 at 04:38
  • 1
    @user1371666: multiple use of `++` or `--` on the same variable in a single statement leads to undefined behavior unless they're separated by something that forces ordering (such as comma operator, logical-and, or logical-or). – Jerry Coffin Apr 16 '22 at 04:40
  • 1
    Behavior is undefined if the prefix or postix operator is used more than once without a *Sequence Point* to set the value or each instance with certainty. If the order in which either `++` can take place can't be determined (e.g. is *indeterminate*), the behavior is undefined. – David C. Rankin Apr 16 '22 at 04:40
  • 1
    @user1371666 *undefined behavior* is when you try to do something that the C++ standard has not defined behavior for, period. So literally *anything* can happen. In any case, see [Undefined behavior and sequence points](https://stackoverflow.com/questions/4176328/). – Remy Lebeau Apr 16 '22 at 04:41
  • 1
    Interestingly, sequence points are no longer mentioned in the more recent C++ standards, instead preferring the "sequenced before/after", "unsequenced", and "indeterminately sequenced" phrases. The effect is pretty much identical though. – paxdiablo Apr 16 '22 at 04:44
  • Even if it the effect of your code seems logical, if you have undefined behavior in it, the program could do different things, after recompiling or after compiling with an updated version of the compiler or just after restarting your program. There are some optimizations, which depend on the cache and are not deterministic. Or it could show wrong results with other input values. Avoid undefined behavior at all costs. So no multiple pre- and postfix operators in the same statement. – Sebastian Apr 16 '22 at 04:59
  • Have voted to re-open. The question about sequence points which was proposed as the duplicate is horribly out of date. There is no such thing as a sequence point in the latest C++ standard. – paxdiablo Apr 16 '22 at 04:59
  • Undefined behavior also means that the result of the innocent program is not limited to any order of execution of the operators, but could do totally different things like crashing the program or (in extreme and luckily rare cases) formatting hard drives. Turn on compiler warnings for those. – Sebastian Apr 16 '22 at 05:03
  • 1
    @paxdiablo Please read all the answers in the dupe. That's the canonical answer. Now we're stuck with the billionth incarnation of increment UB. – Passer By Apr 16 '22 at 06:01
  • @PasserBy: my rule is that, for the question to be a dupe, the *question* has to be a dupe. Not that it may be answered deep within the bowels of another question, however tangential said question may be. You're free to vote how you please but you're not free to assume others have the same opinions as you. Having said that, the new dupe proposed (and actioned) seems a much better match. But that's *not* the one I was talking about. – paxdiablo Apr 16 '22 at 07:14
  • @paxdiablo That's the canonical wiki. It covers everything and more than the current dupe. It gave an answer to every version of C++, you will obviously find more than you need if you only care about one version. *That's by design*, which is especially useful when most people won't know there's a difference between versions. – Passer By Apr 16 '22 at 08:18
  • @paxdiablo The current dupe is truly outdated, which was your original reason to reject the canonical. – Passer By Apr 16 '22 at 08:30
  • @PasserBy, the original *question* I considered outdated because it asked specifically about sequence points, which are no longer even mentioned in the standard. The current *dupe* asks simply about the effect of `++x + ++x + ++x`, which is very much an active issue. As I stated before, I'm apply my rules for "duplicate question" to the question itself. – paxdiablo Apr 16 '22 at 13:41
  • I've had these discussions before and no-one's ever been able to convince me, because of this line of reasoning: "Is a question about the origins of Donald Knuth's MIX language considered a duplicate of one regarding his algorithms in TAOCP, simply because the former has an *answer* for the latter. I think not, since the chance of finding that answer in a largely unrelated question is minimal. *That's* why I only consider the question when judging dupes. It doesn't have to be exact but it does have to be mostly similar. So you can try to convince me but I don't think much of your chances :-) – paxdiablo Apr 16 '22 at 13:47
  • I'm really just explaining *why* I make a particular decision. My way is not necessarily the only true way, that's why the swarm mentality of SO is so great - opinions are averaged out. – paxdiablo Apr 16 '22 at 13:48

2 Answers2

2

Earlier iterations of C++ (and C for that matter) had the concept of sequence points, where it was guaranteed all previous "things" (like the i++ side effect of evaluating that expression) would have completed.

This was replaced at some point with more tightly described "sequencing" along the lines of:

  • if A is sequenced before B, A will be fully done before B starts.
  • if A is sequenced after B, A will be not start until B is fully done.
  • if A and B are indeterminately sequenced, one will complete fully before the other starts, but you won't necessarily know which one went first.
  • if A and B are unsequenced, either may start first, and they may even overlap.

From C++20, [intro.execution], point 10 applies to your code:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.

That means that the individual bits of that full expression may happen in any order, and even overlap, resulting in differing results to what you may think is correct.

In other words, if you want deterministic code, don't do that.


And, despite your findings that the "postfix operator seems logical, it simply updates [the] value and uses it next time [the] variable is used, even when in same statement", that is actually incorrect.

While that may happen in a particular case, the expression (a++) * (a++) is equally as non-determinstic as (++a) * (++a).


If you want to guarantee the sequencing of your operations, you can use more explicit code like:

// int b = (++a) * (++a);
// with pre-increment immediately before EACH use of a:
   int b = (a + 1) * (a + 2); a += 2;
// or:
   int b = ++a; b = ++a * b

There are always ways to achieve the same effect with code that isn't subject to indeterminate results, in the same number of lines of code if you concern yourself with that sort of metric, albeit with two statements on a single line :-)

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • It's not about determinism, it's about UB. – Passer By Apr 16 '22 at 04:49
  • @PasserBy, I have to disagree. If you avoid the UB, your code is deterministic. If you don't, all bets are off. – paxdiablo Apr 16 '22 at 04:52
  • It's either UB or not, what has that got to do with determinism? In fact, the compiler will almost certainly emit a deterministic executable in this case despite the UB. – Passer By Apr 16 '22 at 05:58
  • I should also note this doesn't talk about the sequencing behaviour of pre-increment and determine the (in)validity of the code. – Passer By Apr 16 '22 at 06:04
  • @PasserBy: The compiler is free to do it one way or another depending on *any* criteria. That is the non-deterministic nature (the code may *well* be deterministic from the CPU's point of view but *not* from the programmers). And the pre-increment you claim it doesn't discuss lies within the "evaluations of operands of individual operators" description. – paxdiablo Apr 16 '22 at 07:17
  • [It _is_ UB](https://timsong-cpp.github.io/cppwp/intro.execution#10.sentence-4). Increment semantics lie squarely within the _"Except where noted"_ part, I don't know why you assume it in particular mustn't have different behavior than the default. – Passer By Apr 16 '22 at 08:24
  • 1
    I'm not arguing that any particular code snippet is or is not undefined behaviour (in fact, I agree with you that it is). I simply stated that the code posted leads to non-determinism because you cannot assume it will act in one way or another. – paxdiablo Apr 16 '22 at 14:02
2

From Program execution 4.6.17:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
Example [
int i = 7;
i = i++ + i; //undefined behavior ]

For the same reason you program has undefined behavior.


How can that be explained ?

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.

So the output that you're seeing(maybe seeing) is a result of undefined behavior. And as i said don't rely on the output of a program that has UB. The program may just crash.

So the first step to make the program correct would be to remove UB. Then and only then you can start reasoning about the output of the program.


1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.

Jason
  • 36,170
  • 5
  • 26
  • 60