42

I understand the differences between i++ and ++i, but I'm not quite sure why I'm getting the results below:

static void Main(string[] args)
{
    int c = 42;
    c = c++;
    Console.WriteLine(c);   //Output: 42
}

In the above code, as this is assigning the variable to itself and then incrementing the value, I would expect the result to be 43. However, it is returning 42. I get the same result when using c = c--; as well.

I realise I could just simply use c++; and be done with it, but I'm more curious why it is behaving the way that it is. Can anyone explain what's happening here?

Siyual
  • 16,415
  • 8
  • 44
  • 58
  • 3
    First you declare "c" and it equals to 42. Then you modify its value on "c++", therefore, it will first execute the assignement ("c = c") then increment the value of the right-hand "c", but the thing is that this is a value type, not a reference type, so the incremented value is not the same as the one contained in your final "c". – ssougnez Nov 18 '15 at 15:29
  • 5
    http://stackoverflow.com/a/3346729/447156 – Soner Gönül Nov 18 '15 at 15:30
  • 15
    It took me a bit to realize that we're working in a language where that expression has defined behavior. Anyone working in C or C++, don't try to apply this question's answers to those languages! – user2357112 Nov 18 '15 at 17:26
  • 3
    Actually, just don't use that construct at all in any language even if it is defined behaviour. Because it is confusing in all languages. – Anonymous Coward Nov 18 '15 at 21:18
  • 6
    "_I realise I could just simply use c++_" But... but it's undefined behaviour in c++! – Tas Nov 19 '15 at 05:24
  • 1
    I was about pulling the undefined-behaviour-card and building up nerd-rage as for the existing answers (because of multiple assignments to the same variable within the same statement), but then realised it is not a C++ question. I wouldn't use such statement in practice, though. It force people to look into the docs or got to stack overflow. – Sebastian Mach Nov 19 '15 at 09:25

6 Answers6

38

Let’s take a look at the intermediary language code for that:

IL_0000:  nop
IL_0001:  ldc.i4.s    2A
IL_0003:  stloc.0     // c
IL_0004:  ldloc.0     // c

This loads the constant integer 42 onto the stack, then stores it into the variable c, and loads it immediately again onto the stack.

IL_0005:  stloc.1
IL_0006:  ldloc.1

This copies the value into another register, and also loads it again.

IL_0007:  ldc.i4.1
IL_0008:  add

This adds the constant 1 to the loaded value

IL_0009:  stloc.0     // c

… and stores the result (43) into the variable c.

IL_000A:  ldloc.1
IL_000B:  stloc.0     // c

Then the value from the other register is loaded (that’s still 42!) and stored into the variable c.

IL_000C:  ldloc.0     // c
IL_000D:  call        System.Console.WriteLine
IL_0012:  nop
IL_0013:  ret

Then the value (42) is loaded from the variable, and printed.


So what you can see from this is that while c++ increments the variable by one after the result was returned, that incrementing happens still before assinging the value to the variable. So the sequence is more like this:

  1. Get value from c
  2. Post-increment c
  3. Assign previously read value to c

And that should explain why you get that result :)


To add one more example, since this was mentioned in a comment that was since deleted:

c = c++ + c;

This works very similarly: Assuming an initial value of 2 again, the left side of the addition is evaluated first. So the value is read from the variable (2), then c is incremented (c becomes 3). Then the right side of the addition is evaluated. The value of c is read (now 3). Then the addition takes place (2 + 3) and the result (5) is assigned to the variable.


The takeaway from this is that you should avoid mixing increment and decrement operations in normal expressions. While the behavior is very well defined and makes absolute sense (as shown above), it is still sometimes difficult to wrap your head around it. Especially when you assign something to the same variable that you increment in the expression, this becomes confusing quickly. So do yourself and others a favor and avoid increment/decrement operations when they are not completely on their own :)

poke
  • 369,085
  • 72
  • 557
  • 602
  • Ah! This makes perfect sense now. So essentially, it's setting the variable equal to itself and then increments a copy of the original variable, thus getting lost? – Siyual Nov 18 '15 at 15:36
  • 12
    It does increment the real variable, but it does so before assigning the (old) value to the variable in the assignment statement. So the effect from the post-increment is immediately overwritten by the assignment. – poke Nov 18 '15 at 15:37
  • when will the incremented value be assigned to the the variable c? can you use the same example as asked in the question and extend the code.@poke – Nilesh Jain Jan 02 '20 at 08:52
  • @NileshJain I am not sure what you mean. My answer is almost exclusively about the `c = c++` example. The incremented value will never be assigned to the variable since the assignment is applied with the old value but *after* it has been incremented. – poke Jan 02 '20 at 09:36
  • @poke what i want to know is that what happens to the updated value which is not assigned to the variable. Is it lost? It maybe a dumb question but still wanted to know. – Nilesh Jain Jan 03 '20 at 13:24
  • @NileshJain In `IL_000B` above, the updated value is stored in the variable and the value is removed from the stack, but then immediately after that, in `IL_000C`, the old value is also stored in the variable (and removed from the stack). So yes, that incremented value is gone when the statement completes. – poke Jan 03 '20 at 13:50
  • Do a favour to yourself ..... :D !!! Yes absolutely true – HanniBaL90 Apr 26 '21 at 15:40
29

According to the MSDN page on C# operators the assignment operator (=) has lower precedence than any primary operator, such as ++x or x++. That means that in the line

c = c++;

the right hand side is evaluated first. The expression c++ increments c to 43, then returns the original value 42 as a result, which is used for the assignment.

As the documentation you linked to states,

[The second form is a] postfix increment operation. The result of the operation is the value of the operand before it has been incremented.

In other words, your code is equivalent to

// Evaluate the right hand side:
int incrementResult = c;   // Store the original value, int incrementResult = 42
c = c + 1;                 // Increment c, i.e. c = 43

// Perform the assignment:
c = incrementResult;       // Assign c to the "result of the operation", i.e. c = 42

Compare this to the prefix form

c = ++c;

which would evaluate as

// Evaluate the right hand side:
c = c + 1;                 // Increment c, i.e. c = 43
int incrementResult = c;   // Store the new value, i.e. int incrementResult = 43

// Perform the assignment:
c = incrementResult;       // Assign c to the "result of the operation", i.e. c = 43
CompuChip
  • 9,143
  • 4
  • 24
  • 48
  • 2
    The reason is the order in which things happen. The incrementing is part of the expression, so the expression `c++` would increment c to 43 and return 42, but both things happen before the assignment back to c. So likely (apart from any compile time optimization), c first becomes 43, which is then overwritten to make it 42 again. Your code sample explains this well, but the emphasis doesn't. The emphasised bit just says that the expression will return 42 rather than 43.OP knows this, but expected the incrementing to take place after the assignment. – GolezTrol Nov 19 '15 at 07:09
  • This really helped me understand why Post/Pre increment operators are called so, respectively. In post-increment the result is stored first - and then incremented, whereas in pre-increment its first incremented and then stored. Thanks. – Max Jun 22 '21 at 07:57
5

The docs say for postfix state:

The result of the operation is the value of the operand before it has been incremented.

This means that when you do:

c = c++;

You're actually re-assigning 42 to c, and that's why you're seeing the console print 42. But if you do:

static void Main(string[] args)
{
    int c = 42;
    c++;
    Console.WriteLine(c);  
}

You'll see it output 43.

If you look at what the compiler generates (in Debug mode), you'll see:

private static void Main(string[] args)
{
    int num = 42;
    int num2 = num;
    num = num2 + 1;
    num = num2;
    Console.WriteLine(num);
}

Which shows the overwrite more clearly. If you look at Release mode, you'll see the compiler optimize the entire call to:

private static void Main(string[] args)
{
    Console.WriteLine(42);
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    Shouldn't it then increment `c` at the end of the expression, though? – Siyual Nov 18 '15 at 15:30
  • 2
    @Siyual It first evaluates the right hand side, which increments `c`, but then immediately assings `42` to it, overriding the increment operation. – Yuval Itzchakov Nov 18 '15 at 15:31
  • 1
    @Siyual, the line should be read as `c = (c++)`, not `(c = c)++` (which is not even legal C#, but makes sense only conceptually if you reduce `c = c` to just `c`). – CompuChip Nov 18 '15 at 15:32
4

...as this is assigning the variable to itself and then incrementing the value...

No it's not what it does.

The post-increment operator increments the variable and returns the old value. The pre-increment operator increments the variable and returns the new value.

So your c++ increments c to 43, but returns 42 which then is assigned to c again.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
3

The expression on the right-hand-side of an assignment is evaluated completely, then the assignment is performed.

   c = c++;

Is the same as

   // Right hand side is calculated first.
   _tmp = c;
   c = c + 1;

   // Then the assignment is performed
   c = _tmp;
3

I think I see what the original questioner was thinking. They were thinking (I think) that postincrement means increment the variable after the evaluation of the whole expression, e.g. that

x = a[i++] + a[j++];   // (0)

is the same as

{ x = a[i] + a[j] ; i += 1 ; j += 1; }   // (1)

(and admittedly these are equivalent) and that

c = c++;  // (2)

means

{ c = c ; c +=1 ; } // (3)

and that

x = a[i++] + a[i++];  // (4)

means

{ x = a[i] + a[i] ; i += 2 ; } // (5)

But this is not the case. v++ means increment v right away, but use the old value as the value of the expression. So in the case of (4) an actually equivalent statement is

{int t0 = a[i] ; i += 1 ; int t1 = a[i] ; i += 1 ; x = t0 + t1 ; } // (6)

As others have noted, statements like (2) and (4) are well defined in C# (and Java), but they are not well defined in C and C++.

In C and C++ expressions like (2) and (4) that change a variable and also use it in some other way are usually undefined, meaning the compiler is welcome (as far as the language laws go) to translate them in any way at all, e.g. to transfer money from your bank account to the compiler writer's.

Theodore Norvell
  • 15,366
  • 6
  • 31
  • 45