在网上看到一道题:
int j = 0;
j += j > 0 ? j++ : j--;
原题中变量名是 i ,语言是C++,正确答案为-1(我用VC++验证过就是-1)。
这里我将变量名改为 j ,在C#上验证,结果为0,让我百思不得其解,只得通过IL来了解该表达式详细的计算过程。
同时这也算是学习IL的一个契机(说ji不带ba,文明你我他)。
-------------------------------------割割割割割-----------------------------------------
接下来开始从IL入手,首先要知道.net执行IL指令进行计算时需要的两个栈:
Call Stack(调用堆栈):调用堆栈:调用堆栈是一个方法列表,按调用顺序保存所有在运行期被调用的方法。我们需要注意,进入方法后调用堆栈上会压入一个局部变量列表
Evaluation Stack(计算堆栈):每个线程都有自己的线程栈,IL 里面的任何计算,都发生在 Evaluation Stack 上。可以 Push,也可以 Pop。
接下来开始看IL执行顺序: 指令上方的注释解释接下来的指令要做的事,指令后面跟着的注释解释指令的含义.
//声明局部变量j,相当于执行 int j;
.locals init ([0] int32 j)//在调用堆栈中的局部变量列表中定义int32型变量j(索引号为0,类型为int32,名称为j)
IL_0000: nop//No Operation,无操作
//开始执行 j = 0;
IL_0001: ldc.i4.0//将整数值 0 作为 int32类型压入到计算堆栈(ldc是指令名,i4表示元素类型是4字节元素,0表示元素值)
IL_0002: stloc.0//弹出计算堆栈栈顶并将其存储到索引 0 处的局部变量列表中(stloc为指令名,0为局部变量列表中的索引号,该指令将整数0存入局部变量j)。
//完成赋值
//由于j += j > 0 ? j++ : j--;等价于j = j + j > 0 ? j++ : j--;假如我们将j = j + j > 0 ? j++ : j--;中等号右边第1个j叫做 j1,第二个j叫做 j2,
//则IL_0003和IL_0004分别将 j1 和 j2 的值压入计算堆栈
IL_0003: ldloc.0//将局部变量列表中索引 0 处的局部变量(j)的值压入计算堆栈。
IL_0004: ldloc.0//同上.
//将j = j + j > 0 ? j++ : j--;中大于号右边的0压入计算堆栈
IL_0005: ldc.i4.0//将一个整数0压入计算堆栈.
//执行逻辑运算 j2 > 0
IL_0006: bgt.s IL_000f//弹出IL_0004和IL_0005,如果第一个值大于第二个值,则将控制转移到IL_000f[很显然0>0=false,所以不跳转]。(参考https://msdn.microsoft.com/zh-cn/library/system.reflection.emit.opcodes.blt_s(v=vs.110).aspx)
//j--操作开始
IL_0008: ldloc.0//将j的值压入计算堆栈。
IL_0009: dup//复制计算堆栈上栈顶元素,然后将副本压入计算堆栈。
IL_000a: ldc.i4.1//将整数 1压入计算堆栈
IL_000b: sub//从计算栈先后弹出两个栈顶元素v1、v2,将v1-v2的结果压入栈中,此时计算栈从栈顶到栈底数据为:-1,0,0。
IL_000c: stloc.0//把计算堆栈栈顶元素值赋给j,即j=-1,此时计算堆栈中剩余元素为:0,0。
//j--操作结束,跳转到IL_0014
IL_000d: br.s IL_0014//无条件地将控制转移到IL_0014
//j++操作开始
IL_000f: ldloc.0//将局部变量列表中索引 0 处的局部变量(j)加载到计算堆栈上,此时计算堆栈上有2个j的值。
IL_0010: dup//复制计算堆栈上栈顶元素,然后将副本推送到计算堆栈上,此时计算堆栈上有3个j的值。
IL_0011: ldc.i4.1//将整数值 1 作为 int32 (i4是指4字节)推送到计算堆栈上
IL_0012: add//从计算栈先后弹出两个栈顶元素v1、v2,将v1+v2的结果压入栈中,此时计算栈从栈顶到栈底数据为:1,0,0。
IL_0013: stloc.0//把计算堆栈栈顶元素值赋给j,即j=1,此时计算堆栈中剩余元素为:0,0。
//j++操作结束
//当控制跳转到IL_0014时,计算堆栈中只有原 j1 、j2 的值。注意,j++ 和 j-- 操作结果影响的是 局部变量中的 j 的值,而 j1 和 j2 再执行自减操作前就已经压入计算堆栈了
IL_0014: add//弹出计算堆栈中剩余的俩元素0、0并相加,将结果入栈,此时计算堆栈中仅有1个元素0。
//将计算结果赋值给局部变量列表中的 j,整个计算结束。
IL_0015: stloc.0//把计算堆栈中元素0弹出并赋给j,即j=0。
----------------------------------------------扩展-------------------------------------------------------
通过上面的IL解读可以看出原题的实际执行过程为:
int j;
j = 0;
int j1 = j;
int j2 = j;
if ( j > 0)
{
j++ ;
} else
{
j-- ;
}
j = j1 + j2;
可以看到,某个运算符开始运算时, 代码中参与运算的值类型变量实际是变量的副本,那引用类型变量呢?
扩展:
int i = 3;
i += i++;
根据上面IL执行顺序,你觉得 i 的值是多少?