引子:今天早上早早醒来无事,上园子依次看到
其中最后一篇 引起了我的好奇,难道CSC还真有bug?我来看看。
其实就是很简单的一个程序,
2 int b = 9;
3 a ^= (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:
2 {
3 .entrypoint
4 .maxstack 4
5 .locals init (
6 [0] int32 a,
7 [1] int32 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 |
|
|
|
|
|
|
|
|
其对应的汇编码是:
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++编译器中则是能成功交换的,汇编码如下:
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 的实际位置。
结论:不同 的语言有着不同的解释方式,有可能看着一样的代码,在不同的编译器下会产生不同的结果,你知道下面的代码结果是什么吗?
2 int j=(i++)+(i++);
3 j=?
希望能给大家提供一些帮助。