0
// Example program
#include <iostream>
#include <string>

int main()
{
 int i=1;   
 int x;
 x = ++i + ++i + ++i;
 std::cout<< x;
 return 0;
}

////////Why am I getting 10 and not 9 ?
/*  if i starts at 1, increment operator takes precedence
so that means 2 3 4  then 2+3+4 = 9  not 10 ??
so how am I getting 10 ??
*/
2
Contributors
6
Replies
27
Views
1 Year
Discussion Span
Last Post by AssertNull
0

Rule 1 when experimenting with operator precedence. Do one thing at a time. You have five operations going on here. They clearly aren't happening in the order you think they are.

Change the line to this and see what happens:

x = i + ++i;

To really see what's going on, check out the disassembly in the debugger:

7    int i=1;
0x004013F6  movl   $0x1,-0xc(%ebp)
8    int x;
9    x = ++i + ++i + ++i;
0x004013FD  addl   $0x1,-0xc(%ebp)
0x00401401  addl   $0x1,-0xc(%ebp)
0x00401405  mov    -0xc(%ebp),%eax
0x00401408  lea    (%eax,%eax,1),%edx
0x0040140B  addl   $0x1,-0xc(%ebp)
0x0040140F  mov    -0xc(%ebp),%eax
0x00401412  add    %edx,%eax
0x00401414  mov    %eax,-0x10(%ebp)
10   std::cout<< x;
0x00401417  mov    -0x10(%ebp),%eax
0x0040141A  mov    %eax,(%esp)
0x0040141D  mov    $0x6fcc43c0,%ecx
0x00401422  call   0x4014a4 <std::ostream::operator<<(int)>
0x00401427  sub    $0x4,%esp

Okay, let's see what's up. Look at this part in particular.

    0x004013FD  addl   $0x1,-0xc(%ebp)
    0x00401401  addl   $0x1,-0xc(%ebp)

Those are the first two things that happen. i gets incremented TWICE right off the bat. So i is 3 now from the first two ++'s.

NOW we add:

0x00401405  mov    -0xc(%ebp),%eax
0x00401408  lea    (%eax,%eax,1),%edx

3 + 3 is 6.

0x0040140B  addl   $0x1,-0xc(%ebp)

Now we increment i again. i is now 4.

0x0040140F  mov    -0xc(%ebp),%eax
0x00401412  add    %edx,%eax

Now we add: 6 + 4 = 10.

See the order of precedence...

http://en.cppreference.com/w/cpp/language/operator_precedence

Now I DID get a warning at compile-time.

warning: operation on 'i' may be undefined [-Wsequence-point]

Not sure what that's about. It compiled exactly how I expected. Maybe it's not guaranteed? I had thought it was and it is in the order that it is in the link above. I'm actually confused on the warning since I thought the result would be unambiguously 10 on all compilers.

0

Okay, I think I may understand what the ambiguity might be. If someone with more compiler knowledge and knowledge of the C++ spec can chime in, I'd appreciate it.

Take a line like this:

x = ++i + ++i;

The specification requires that both increments must take place before adding. However, it makes no stipulation about how the compiler handles the registers. My compiler used the lea command. However, it could have used two separate registers (let's say eax and ebx):

  1. increment i: i = 2
  2. increment i: i = 3
  3. copy i(3) to eax register
  4. copy i(3) to ebx register
  5. add eax and ebx registers (3 + 3 = 6)

Result is 6.

The C++ standard requires that step 1 comes before step 3, step 2 comes before step 4, and steps 3 and 4 come before step 5. However it does NOT say that step 2 must come before step 3, so you could have this...

  1. increment i: i = 2
  2. copy i(2) to eax register
  3. increment i: i = 3
  4. copy i(3) to ebx register
  5. add eax and ebx registers (2 + 3 = 5)

Hence the compiler warning? Can anyone back this interpretation up or clarify? I do notice that if I change the OP's code to the following, I get no warnings and a result of 6.

// Example program
#include <iostream>
#include <string>

int main()
{
 int i=1;
 int j = 1;
 int k = 1;
 int x = ++i + ++j + ++k;
 std::cout<< x;
 return 0;
}
1

Okay, one more post. According to this link:

http://en.cppreference.com/w/cpp/language/operator_incdec

if you go about halfway down, they have an example. Key l;ine of code is below:

//  int n6 = n1 + ++n1; // undefined behavior

Here is the program.

#include <iostream>

int main()
{
    int n1 = 1;
    int n2 = ++n1;
    int n3 = ++ ++n1;
    int n4 = n1++;
//  int n5 = n1++ ++;   // error
//  int n6 = n1 + ++n1; // undefined behavior
    std::cout << "n1 = " << n1 << '\n'
              << "n2 = " << n2 << '\n'
              << "n3 = " << n3 << '\n'
              << "n4 = " << n4 << '\n';
}

The ++n1 must be executed before adding since the pre-increment takes precedence over addition. That is the ONLY thing that the spec sets in stone. The left addend and right addend must each be "evaluated" before adding them together, but there is nothing that says which side (left or right) must be "evaluated" first. Hence undefined behavior. The moral is that if you are incrementing the same scalar variable more than once in a statement (or any other operations with multiple "side-effects" on the same scalar variable in the same statement), it's better to break it into two or more statements in order to prevent undefined behavior.

Votes + Comments
You are Brilliant :). So this is compiler dependent and all around bad programming practice.
0

I am still struggling to find a logic here of predictability on the output, but maybe I need to let this one go, and toss it away as very bad programming example and it will result in inconsistent behavior. I dont like undefined behavior lol :) I think everything can be defined lol but maybe the catch 21 is this is defined as undefined a vicious loop of confusions lol.

Thinks AssertNull I could just see into your Brilliant mind trying to make a logic on this one too, and help another human being understand it. Your efforts and time spent are appreciated beyond words !

0

You're welcome. Here is a good general approach when learning to code in C++. All languages must make decisions regarding trust/safety versus execution speed. The key to understanding the mindset of the folks who created/maintain/use C and C++ is that they defer to the programmers to know what they are doing and nothing must slow down execution speed. You have a small set of rules that you are expected to follow as a C/C++ coder. You are assumed to be responsible and smart enough to follow those rules and understand the consequences of not following those rules. That's why they are "warnings", not "errors". One accidental buffer overflow and you can completely wipe out the operating system. The compiler will hopefully warn you that something is undefined behavior and you are supposed to look at the code and see whether it needs fixing. If you decide that it is no big deal, the compiler shrugs and says "Oooookay, hope you know what you're doing". It does not feel any need to FORCE you to not ignore this warning or second guess you. This is a completely different approach from, say, Java, which forces you to "fix" or "handle" everything even if it isn't broken. For example, you could write and execute the following C++ code and wipe out your hard drive on accident.

#include <iostream>
#include <string>
using namespace std;

int main()
{
    int a[2] = {0, 1};
    int x = 5;
    a[2] = 50000; // index out of bounds, wipes out x value
    if(x == 50000)
    {
        cout << "Uh oh";
        // execute code to wipe out my hard drive
    }
    return 0;
}

Java on the other hand does error-checking on the a[] array indexes at runtime and would throw an exception. Your hard-drive would be safe. Java doesn't trust you, so it checks for you. In fact, sometimes it finds problems that aren't even there and you have to "fix" them. Safety trumps execution speed in Java, unlike in C++.

Moral of the story is that there's a chunk of things that are defined as "undefined behavior" in C++. "Undefined behavior" means just that. Anything, absolutely anything goes. Compiler writers can handle that code however they like and they don't check at all to make sure it doesn't screw anything up. That's on you, the programmer. When you code with C++ and do something fancy, it's sometimes the equivalent of rewiring your house with the power on, no circuit breakers, no fuses. If you know what you're doing, you're fine. Maybe. The C++ compiler writer will almost always parse your code and turn it into machine code on the basis of optimization (speed, code size, memory usage, whatever) and that will vary depending on machine architecture and other things possibly outside of the programmer's control. It's even conceivable that the same compiler compiling on the same machine might compile a function differently depending on a small change somewhere else in the code. An example might be a function was called only once in the code. The compiler might optimize that function to be an inline function and an out-of-bounds array might not be a problem. A second function call might remove that inline optimization and the out-of-bounds array could corrupt the stack and crash the program or worse. Same C++ code, same mistake, but one time you get away with it and one time it's a disaster due to different optimizations by the same compiler on the same computer.

So, different results from different compilers. Best bet is to simply not do anything with undefined behavior, which means you sometimes need to break a statement into smaller statements. Your code (incrementing the same variable more than once in the same statement) was undefined behavior. Not only could one compiler print out 9 and another print out 10, but a third compiler could print out 12 million (highly doubtful, but it COULD).

This topic has been dead for over six months. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.