3

Given...

int a = 1, b = 4;

Then...

a += b += a += b;  

Is evaluated in C++...

(a += (b += (a += b))); // a = 14[5 + 9] (b = 9[4 + 5] (a = 5[1 + 4]))  

... and in C#...

(a += (b += (a += b))); // a = 10[1 + 9] (b = 9[4 + 5] (a = 5[1 + 4]))  
                        //        ^ re-use of original value of a (1)  
                        //          instead of expected intermediate right-to-left  
                        //          evaluation result (5)  

The above has been tested in both Visual Studio 2008 and 2012, so there is no suspicion of a recently introduced language bug.

However, I did expect C#'s behaviour to mimic C++'s and assume I need education. I understand the HOW of the expression evaluation logic and don't need MSIL explained. Having searched fairly extensively and failed to find relevant fine print in the language specification, I was hoping a language expert would explain WHY this is so.

And for the curious who want to know why on earth would I want to do this... There's a handly little C++ trick for a pretty efficient and tidy inline swap of two integral types which goes like this...

a ^= b ^= a ^= b;  

I am disappointed to find it doesn't work in C# and curious as to why. This is about understanding C#'s low-level mechanics and rationale behind them. No more, no less and specifically no readability religion please.

NB Please folks, this NOT a serious effort at squeezing everything onto one line!!!
For C (and later C++) operator precedence and associativity have always been precisely defined. The standard right-to-left associativity for assignment operators makes the C/C++ version's behaviour 100% clear and predictable. It has worked for me on several compilers and platforms and I would consider a compiler that didn't behave like this to be faulty.
I acknowledge that the line of code is obfuscated, it is a curiosity and used as a "brain teaser" I might give a junior C/C++ programmer. Clearly C# is different - and apparently deliberately so.
I seek an explantion of the design considerations that led to C#'s behavioural divergence from one of its ancestor languages. Even educated guessing would be welcomed.

Answered Thanks to Alexander Stepaniuk.
Start with left-to-right evaluation of operands

a        = 1 + theRest1     // (1)  
theRest1 = b = 4 + theRest2 // (2)  
theRest2 = a = 1 + 4        // (3)  
(3) into (2): theRest1 = b = 4 + 5  
(2) into (1): a = 1 + 9  

A "WHY" explanation would still be appreciated.
But the C# specification is clear that the above is the correct evaluation.
And below is as close as we can get (I think) using 2 variables, to the C++ trick...

b ^= a ^= b;
a ^ = b;

I don't like it - for the record - because it breaks my intuition ;-)

AlanK
  • 1,827
  • 13
  • 16

3 Answers3

4

It's not guaranteed to work in C/C++ either. The value of a variable assigned more than once in a single statement is undefined.

That it works is down to implementation - you might just as well find a compiler or architecture where it doesn't work at all.

C# is just being strict about it, which is no bad thing.

The xor trick is pointless on a modern architecture anyway. The underlying implementation will be less efficient than just using a temporary variable. There are more than 2 registers on a modern CPU.

JasonD
  • 16,464
  • 2
  • 29
  • 44
  • you may have taught me something. I always understood that assignment chaining worked (in C++) precisely (and only) because of the specification that assignment statements always return their assigned value. I was unaware of a rule barring multiple assignments to a single variable within one statement. Do you have a source/link for this tidbit please? – AlanK Nov 24 '12 at 19:11
  • 1
    @AlanK "sequence points" http://stackoverflow.com/questions/4176328/undefined-behavior-and-sequence-points – JasonD Nov 24 '12 at 19:17
4

It even goes back to c.

K&R has this example:

a[i++] = i;

This is undefined behaviour, modifying and using a variable in one line. One compiler can work one way and one another. The standard delibrately leaves it undefined so that you do not rely on it.

K&R recommends using seperate lines or intermediate variables. Not for Human readability, but to make the interpretation unambiguous for the compiler.

a ^= b ^= a ^= b;

So this becomes the unambigious:

a ^= b; b ^= a; a ^= b; // can still on one line if that is important to you!

See here for more undefined behaviours: Why are these constructs (using ++) undefined behavior?

Community
  • 1
  • 1
weston
  • 54,145
  • 21
  • 145
  • 203
  • The right-to-left associativity of assignment operators in C/C++ is well-defined and my example does not mix in operators with different precedence levels (++, []). – AlanK Nov 24 '12 at 18:55
2

You've actually explained everithing by yourself :)

"re-use of original value of a (1)"

Why does that happen? You can find great exlanation here

In short
a += anything is equivalent to a = a + anything.
So compiler needs to evaluate both operands of expression a + anything and then assign result to a:
1. First operand is evaluated (which is a). Result of evaluation is 1
2. Second operand is evaluated (which is expression). Result of evaluation is 9. Side-effect of evaluation is that a contains 5
3. Sum of first and second operand is evaluated. Result is 10
4. Result is assigned to a. Now a contains 10.

Hope that helps.

UPDATE
According to c# Specification: The order in which operands in an expression are evaluated, is left to right. See §14.2 at page 150.
So, this behavior is specified and correct in C#.

Community
  • 1
  • 1
Alexander Stepaniuk
  • 6,217
  • 4
  • 31
  • 48
  • My question is not what is happening but WHY? Why does C# appear to work right to left thus... assign a new value to a, then a new value to b using the new value of a, then add the old value of a to the new value of b for the final assignment? C# operator precedence and associativity rules appear the same as C/C++ for assignments. So why the behavioural difference? – AlanK Nov 24 '12 at 19:25
  • I've updated my answer with reference to specification. C# evaluates binary expressions *left to right* that is why the old one value of `a` is used. – Alexander Stepaniuk Nov 24 '12 at 19:52
  • Thanks. That's what I needed. I've updated my original question in terms of this new understanding. (Can't say it is intuitive to me yet but it is understood.) – AlanK Nov 25 '12 at 19:16