通过IL了解.Net中表达式计算过程

本文通过一个C#中的表达式计算问题,探讨了在.NET中表达式是如何通过IL(中间语言)进行计算的。作者详细分析了IL指令,揭示了计算过程中Call Stack和Evaluation Stack的作用,并对引用类型和值类型变量在运算中的行为进行了讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在网上看到一道题:

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 的值是多少?

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值