Dmitry Kolchev

Hello,

int a;

a = 1;
a += a += a += a += 1;
Console.WriteLine("Result1={0}", a);

a = 1;
a += 1;
a += a;
a += a;
a += a;
Console.WriteLine("Result2={0}", a);

Before running code above I think the first and the second result will be the same. After running this code I've been surprised the results are different (Result1=5, Result2=16). In my point of view the correct result in the first case is 16. Also in C# does not work standard C variable exchange expression c^=d^=c^=d;

Result of execution code below is 0-10 (must 20-10)

int c = 10;
int d = 20;
c ^= d ^= c ^= d;
Console.WriteLine("{0}-{1}", c, d);

Could you please tell me is this a bug

Thank you.



Re: Visual C# Language right-associative operator compiler bug

Bashmohandes

I don't think this is a bug, I think it has something to do with IL code generation, which may be different in the two cases for some reason or another, anyway, I'm very interested to know what are those reasons





Re: Visual C# Language right-associative operator compiler bug

Peter Ritchie

You might first want to start out with your expressions in the same order. Your second += example has the += 1 at the start; where the first is at the end. Comparing apples and oranges...

It's not a bug; what you're seeing is the effect of operator precedence an associativity. Equality operators have an associativity of right to left, meaning evaluation is performed right to left. Going from right to left with a += a += a += a += 1 where a == 1, results in something like : a += 1 += 1 += 1 += 1 (although not syntactically valid), resulting in 5 because a has to be evaluated before the operator is executed. Whereas your multi-line equivalent means the next evaluation of a occurs after the previous assignment, resulting in 16.

C# is not C, it doesn't do many things like C/C++. Don't expect the same complex expressions to have the same results as those in C/C++. If you're unsure, don't use complex expressions (recommended) or parenthesise to ensure the result your looking for (which isn't valid with +=).

As with ^=, associativity and precedence is often different than with C/C++. a ^= d ^= c ^= d exchanges as a side-effect, not by design; it's a hack and extremely unreadable and arguably should be avoided in C.






Re: Visual C# Language right-associative operator compiler bug

Marcelo Guerra - MSFT

Hi Peter,

Actualy, today this is a bug, in the sense that it's on our bug database. However, it's interesting that you see this as expected behavior. Could you give me a "legal" (a.k.a. spec based) argument to support your explanation, specially the point "because a has to be evaluated before the operator is executed"

Thanks in advance for your help.





Re: Visual C# Language right-associative operator compiler bug

IsshouFuuraibou

I think it stems from an understanding of when you want to evaluate the variable "a" in the command line into its current value. From actions like pre-increment and post-increment we have the assumption that before any calculation is done in the line that the variables are evaluated, then pre-incremented, then the order of operations takes place.

I'd have to do more looking into what the IL generated code would be, but remember that
a += a;
is supposed to be the same code as
a = a + a;

so going from
a = 1;
a += a += a += a += 1;
you should get a identical equation of
a = a + a + a + a + 1;
// substitute 1 for all a's

When you evaluate the value of a, they all become 1, then you have 5 as the result.

Now since the += (-=, /=, *=, etc) operations are really language definitions they can be defined to occur in a specific order and contain cause and effect.

Going from the other:
a = 1;
a += 1;
a += a;
a += a;
a += a;
you're equivalent single equation is actually:
a = a + 1 + (a + 1)
+ ((a + 1) + (a + 1)) + ((a + 1) + (a + 1) + (a + 1) + (a + 1)); // substitute 1 for all a's

your result for this equation should be 16, the same you get from the four equations above.

That's just my take on the issue at hand. Any comments on this opinion

Here's the IL disassembly for the two sets of statements, as you can see they are radically diferent:

            int a = 1;
00000000  push        ebp 
00000001  mov         ebp,esp
00000003  sub         esp,10h
00000006  push        edi 
00000007  push        esi 
00000008  push        ebx 
00000009  mov         dword ptr [ebp-4],ecx
0000000c  mov         dword ptr [ebp-8],edx
0000000f  xor         esi,esi
00000011  mov         esi,1
            a += a += a += a += 1;
00000016  mov         dword ptr [ebp-10h],esi
00000019  mov         edi,esi
0000001b  mov         ebx,esi
0000001d  inc         esi 
0000001e  add         esi,ebx
00000020  add         esi,edi
00000022  add         esi,dword ptr [ebp-10h]
            a = 1;
00000025  mov         esi,1
            a += 1;
0000002a  inc         esi 
            a += a;
0000002b  add         esi,esi
            a += a;
0000002d  add         esi,esi
            a += a;
0000002f  add         esi,esi

one more disassembled code:
            a = a + a + a + a + 1;
00000025  lea         eax,[esi+esi]
00000028  add         eax,esi
0000002a  lea         eax,[eax+esi+1]
0000002e  mov         esi,eax

appears to be able to convert this to a better optimized code than the a += a += a += a += 1 code.





Re: Visual C# Language right-associative operator compiler bug

Dmitry Kolchev

If this behavior is by design, any programmer can run into problem of porting C/C++ code to C#. Also I don't think a^=b^=a^=b value exchange is "side-effect" I suppose this is fundamental feature of XOR.



Re: Visual C# Language right-associative operator compiler bug

IsshouFuuraibou

Whether it is by design, effect of compilation, or a bug.

Since we need to assume that the compiler will try and convert one line of code to one set of operations, while it will take multiple lines of code and convert each of them into their own set.

I would personally avoid cases where you are doing multiple assignment operations in a line which model C/C++ code. Those usually were shortcuts in C/C++ that could often be problematic, especially if different compilers produced different code.

Even though it is true that a ^= b ^= a ^= b does perform a value exchange in C/C++, other programmers from different backgrounds won't see that immediately. I don't see it as a side-effect of XOR, but of a side effect of the XOR assignment operation and precedence. I can't say whether that was the original design for ^= or if a creative programmer discovered that use. My background is Java and C#. To me that method is a lax way of doing value swapping just to avoid an intermediate value.

a = 15;
...
00000019 mov esi,0Fh
int b = 243;
0000001a mov edi,0F3h
a ^= b ^= a ^= b;
0000001f mov dword ptr [ebp-18h],esi
00000022 xor esi,edi
00000024 xor edi,esi
00000026 mov eax,dword ptr [ebp-18h]
00000029 xor eax,edi
0000002b mov esi,eax
a = 15;
0000002d mov esi,0Fh
b = 243;
00000032 mov edi,0F3h
int xa = a ^ b;
00000037 mov eax,esi
00000039 xor eax,edi
0000003b mov ebx,eax
b ^= xa;
0000003d xor edi,ebx
a ^= xa;
0000003f xor esi,ebx

// Third method
xa = b;
0000004b mov ebx,edi
b = a;
0000004d mov edi,esi
a = xa;
0000004f mov esi,ebx

in the first a ^= b ^= a ^= b you end up with a = 0 and b = 15
in the second you end up with the values actually swapped. The compiler seems to do better with the multiple lines in terms of number and length of operations.
The third has the potential to be the most efficient, provided it doesn't need to go to memory, moving values between registries is a fairly quick operation.

C# is inherently different from C/C++. If the issue is porting C/C++ code to C#, one could argue that as they port the older code the ported code should be done in proper C# and not trying to use the old shortcuts from C/C++. Frankly there are a number of shortcuts from C/C++ that don't work in C# because of the underlying decision for type safety, memory management checking array bounds, and other foundations C# was based upon.





Re: Visual C# Language right-associative operator compiler bug

GheFly

Please do not forget that even in C/C++ operator associativity and time evaluation order of an expression are two different things.

Time evaluation order is guarantted only for &&, II, : and ,

Using a "C" language, the first expression seems to be "implementation-dependant". Compilers are free to optimize it as they wish.





Re: Visual C# Language right-associative operator compiler bug

micvos

I must agree with Peter. This is not a bug. Altough the assignment operator is right associative the c# grammar is specified from left ot right. This means that the most left assignment operator is evaluated first before the ones to the right. This is a problem which occurs even difficult to compiler builders. For example this problem occurs also for java compilers which uses roughly the same grammar. I have seen a number of java compiler clones which use a different evaluation order than that of the sun compiler. This is also specified in the c# specification altough it's difficcult to read:

7.13.2 Compound assignment

An operation of the form x op= y is processed by applying binary operator overload resolution (ˇě7.2.4) as if the operation was written x op y. Then,
If the return type of the selected operator is implicitly convertible to the type of x, the operation is evaluated as x = x op y, except that x is evaluated only once.
Otherwise, if the selected operator is a predefined operator, if the return type of the selected operator is explicitly convertible to the type of x, and if y is implicitly convertible to the type of x or the operator is a shift operator, then the operation is evaluated as x = (T)(x op y), where T is the type of x, except that x is evaluated only once.
Otherwise, the compound assignment is invalid, and a compile-time error occurs.
The term ˇ°evaluated only onceˇ± means that in the evaluation of x op y, the results of any constituent expressions of x are temporarily saved and then reused when performing the assignment to x. For example, in the assignment A()[B()] += C(), where A is a method returning int[], and B and C are methods returning int, the methods are invoked only once, in the order A, B, C.

Pay attention to the last section in which it says the evaluation order is form left to right and that intermediate values are temporarily saved (which means they are not assigned to the original variable).






Re: Visual C# Language right-associative operator compiler bug

Dmitry Kolchev

From my point of view programming language should be consistent

int a = 1;

a += a += a += a += a;

int b = 1;

b = b + (b = b + (b = b + (b = b + b)));

int c = 1;

c = c + c;

c = c + c;

c = c + c;

c = c + c;

Console.WriteLine("a={0} b={1} c={2}", a, b, c);

The second expression is equivalent of the first. The third set of expressions very similar to the second, but result of the third differs from the first and second.

Due to C# implementation in the second expression in the time of l-value evaluation, variable "b" will have different values (except innermost parenses).

I suppose all three variables (a, b, c) should be evaluated to 16.

Thank you.





Re: Visual C# Language right-associative operator compiler bug

Andy

Hi Dmitry.

I think it is not a bug.

Let's see at expressions sequence { int x = 1; int y = 1; x += y += 1; }

It's clear, that last expression is equivalent to { x += y+1; y += 1; } or just { x = 3; y = 2; }

Only the question is: which order { x = 3; y = 2; } or { y = 2; x = 3; } It is not specified for C++.

The same happens when we have { int a = 1; a+=a+=1; } It is not specified, if it is { a = 3; a = 2; } or { a = 2; a = 3; }

Sorry, I don't have C# standard on hand, only C++ ISO/IEC 14882.

Chapter 5, paragraph 4:

"Except where noted, the order of evaluation of operands of individual operators and

subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

...

i = ++i + 1; // the behavior is unspecified

..."

Therefore it is luck that you have a += a += a += a += 1; evaluated by your C++ compiler as you expect.





Re: Visual C# Language right-associative operator compiler bug

James Curran

I don't see how anyone porting from C/C++ would have a problem with that line, since that line is illegal is C & C++.

Also I don't think a^=b^=a^=b value exchange is "side-effect"

In C & C++, the line invokes "undefined behavior".

The rules are simple:

Between sequence points (semi-colons), you can:

1) Look at a value as much as you want, provided you don't change it:

b = a + a + a + a; /// legal

2) Look at it once and change it once in the same statement:

b = a++; // legal

a = a + 1; // legal

b = a++ + a; // ILLEGAL

3) You cannot change it more than once:

b = a++ + a++; // ILLEGAL
a = a++; // ILLEGAL

In a^=b^=a^=b, a is altered twice. It is illegal. If it happened it work the way you wanted on your compiler, good for you. You should not expect it to work anywhere else.






Re: Visual C# Language right-associative operator compiler bug

James Curran

Looking up the C# Specification:

7.2 "Operands in an expression are evaluated from left to right. For example, in F(i) + G(i++) * H(i), method F is called using the old value of i, then method G is called with the old value of i, and, finally, method H is called with the new value of i. This is separate from and unrelated to operator precedence."

So, in other words, in "a = 1; a += a += a += a += 1;" all the a's will be evaluated to 1 before any the additions take place.