你真的了解a ^= (b ^= (a ^= b))吗?

引子:今天早上早早醒来无事,上园子依次看到

[C#] int与System.Int32有什么区别

理解C#中的System.Int32和int:并非鸡和鸡蛋

异或运算的一个问题,疑似C#编译器的Bug?

其中最后一篇 引起了我的好奇,难道CSC还真有bug?我来看看。

其实就是很简单的一个程序,

 

1 int a = 2;
2 int b = 9;
3 ^= (b ^= (a ^= b));
4 Console.WriteLine("{0}, {1}", a.ToString(), b.ToString());

 

就是运行最后的结果是:

0,2

不是预期的9,2,后面的评论只是说a ^= (b ^= (a ^= b))相当于2 ^= (9 ^= (2 ^= 9)),具体的也没说明具体的原因是什么。

首先说明,托管代码在运行,主要用到3种形式的内存:

1.Managed Heap:动态内存分配的地方,由GC来管理,整个进程公用一个托管堆

2.Call Stack:每个thread都有自己的call  stack,每call一个方法,就会增加一个方法帧,方法执行完毕,则帧失效。帧记录了方法的参数,返回地址,局部变量

3.Evaluation Stack:同样每个thread都有自己的evaluation  stack,我们经常说的虚拟堆栈,就是这个。

下面是上述代码的IL:

 

 1 .method private hidebysig static void Main(string[] args) cil managed
 2 {
 3     .entrypoint
 4     .maxstack 4
 5     .locals init (
 6         [0int32 a,
 7         [1int32 b)
 8     L_0000: nop 
 9     L_0001: ldc.i4.2 
10     L_0002: stloc.0 
11     L_0003: ldc.i4.s 9
12     L_0005: stloc.1 
13     L_0006: ldloc.0 
14     L_0007: ldloc.1 
15     L_0008: ldloc.0 
16     L_0009: ldloc.1 
17     L_000a: xor 
18     L_000b: dup 
19     L_000c: stloc.0 
20     L_000d: xor 
21     L_000e: dup 
22     L_000f: stloc.1 
23     L_0010: xor 
24     L_0011: stloc.0 
25     L_0012: nop 
26     L_0013: nop 
27     L_0014: ret 
28 }
29 
30 

我将逐语句的说明实际的执行效果。

 

Evaluation Stack

L_0001: ldc.i4.2

 

Call  Stack

2

 

 

 

 

 

 

 

 

Evaluation Stack

L_0002: stloc.0

 

Call  Stack

 

2

 

 

 

 

 

 

 

Evaluation Stack

L_0003: ldc.i4.s 9

 

Call  Stack

9

2

 

 

 

 

 

 

 

Evaluation Stack

L_0005: stloc.1
 

 

Call  Stack

 

2

 

9

 

 

 

 

 

Evaluation Stack

L_0006: ldloc.0
 

 

Call  Stack

2

2

 

9

 

 

 

 

 

Evaluation Stack

L_0007: ldloc.1
 

 

Call  Stack

9

2

2

9

 

 

 

 

 

Evaluation Stack

L_0008: ldloc.0
L_0009: ldloc.1

 

Call  Stack

9

2

2

9

9

 

2

 

 

Evaluation Stack

L_000a: xor

 

Call  Stack

11

2

9

9

2

 

 

 

 

Evaluation Stack

L_000b: dup

 

Call  Stack

11

2

11

9

9

 

2

 

 

Evaluation Stack

L_000c: stloc.0

 

Call  Stack

11

11

9

9

2

 

 

 

 

Evaluation Stack

L_000d: xor

 

Call  Stack

2

11

2

9

 

 

 

 

 

Evaluation Stack

L_000e: dup

 

Call  Stack

2

11

2

9

2

 

 

 

 

Evaluation Stack

L_000f: stloc.1

 

Call  Stack

2

11

2

2

 

 

 

 

 

Evaluation Stack

L_0010: xor

 

Call  Stack

0

11

 

2

 

 

 

 

 

Evaluation Stack

L_0011: stloc.0

 

Call  Stack

 

0

 

2

 

 

 

 

 

其对应的汇编码是:

 

 1  93:         int a = 2;
 2 00000030  mov         dword ptr [ebp-40h],2 
 3     94:         int b = 9;
 4 00000037  mov         dword ptr [ebp-44h],9 
 5     95:         a ^= (b ^= (a ^= b));
 6 0000003e  mov         eax,dword ptr [ebp-40h] 
 7 00000041  mov         dword ptr [ebp-48h],eax 
 8 00000044  mov         eax,dword ptr [ebp-44h] 
 9 00000047  xor         dword ptr [ebp-40h],eax 
10 0000004a  mov         eax,dword ptr [ebp-40h] 
11 0000004d  xor         dword ptr [ebp-44h],eax 
12 00000050  mov         eax,dword ptr [ebp-48h] 
13 00000053  xor         eax,dword ptr [ebp-44h] 
14 00000056  mov         dword ptr [ebp-40h],eax 

 

可以看到,a和b分别在[ebp-40h]和[ebp-44h]处,在时间xor之前先将a缓存到了[ebp-48h],然后再最后一次xor时,读的是这个缓存。

这只是在C#和.Net中的解释,在C++编译器中则是能成功交换的,汇编码如下:

 

 1 45:     int a = 2;
 2 0033180E  mov         dword ptr [a],2 
 3     46:     int b = 9;
 4 00331815  mov         dword ptr [b],9 
 5     47:     a ^= (b ^= (a ^= b));
 6 0033181C  mov         eax,dword ptr [a] 
 7 0033181F  xor         eax,dword ptr [b] 
 8 00331822  mov         dword ptr [a],eax 
 9 00331825  mov         ecx,dword ptr [b] 
10 00331828  xor         ecx,dword ptr [a] 
11 0033182B  mov         dword ptr [b],ecx 
12 0033182E  mov         edx,dword ptr [a] 
13 00331831  xor         edx,dword ptr [b] 
14 00331834  mov         dword ptr [a],edx 

都是直接访问的a和b 的实际位置。

 结论:不同 的语言有着不同的解释方式,有可能看着一样的代码,在不同的编译器下会产生不同的结果,你知道下面的代码结果是什么吗?

 

1 int i=0;
2 int j=(i++)+(i++);
3 j=?

 

希望能给大家提供一些帮助。

posted @ 2009-06-14 09:53  DiggingDeeply  阅读(2608)  评论(17编辑  收藏  举报