从MSIL角度分析.net中++i和i++具体的区别

来源:互联网 发布:ubuntu 终端打不开 编辑:程序博客网 时间:2024/06/08 19:28

在学.net的第一天估计就已经学到了++i和i++的区别 表现上讲 i++完成后 i并没有被马上赋值为i+1 或者说需要被赋值为i++的值不会得到这个更新后的i值 而++i的话i的值会马上会赋值为i+1 同样需要被赋值为i+1的变量会马上得到更新后的i+1值

从百度知道上是这么解释的

i++ 先执行此行语句再i=i+1++i 先执行i=i+1再执行此行语句 

虽然容易理解 但是编程中经常性的也会出现i++不行的话就试试++i的情况

比如下面这行代码

using System;namespace VimTest{public class VimeTest{public static void Main(string[] arg){                        int i=0;                        int j=0;                        j=i++;                        Console.WriteLine(i);                        Console.WriteLine(j);                        Console.ReadLine();}}}

最后的输出如果不仔细想的话会认为是i=j=1 但是事实上i=1而j=0

如果换一种方式 j=++i 则最后的输出会是i=j=1

好吧其实死记硬背还是记得住的 但是总会想知道背后到底是怎么回事 正好这几天在学习MSIL 所以打算把这行程序反汇编一下看看到底是为什么导致了++在前和在后的区别

首先反汇编j==i++的情况

.method public hidebysig static void  Main(string[] arg) cil managed
{
  .entrypoint
  // 代码大小       32 (0x20)
  .maxstack  3
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0  i 赋值为0 索引0
  IL_0003:  ldc.i4.0 
  IL_0004:  stloc.1  j 赋值为0 所以1
  IL_0005:  ldloc.0 索引0元素i入栈
  IL_0006:  dup      复制栈顶元素的值 复制后的值入栈 
  IL_0007:  ldc.i4.1 数值1入栈
  IL_0008:  add      栈顶两个元素相加 结果入栈
  IL_0009:  stloc.0  出栈 将栈顶元素赋值给索引为0的变量 即i 此时i值为栈顶元素的值1
  IL_000a:  stloc.1  同上 但是此时因为栈顶元素值为0 所以j值为0
  IL_000b:  ldloc.0
  IL_000c:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0011:  nop
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0018:  nop
  IL_0019:  call       string [mscorlib]System.Console::ReadLine()
  IL_001e:  pop
  IL_001f:  ret
} // end of method VimeTest::Main


忽略掉最后几行函数调用 我们来看下i++到底做了什么

首先0到4行是初始化,i和j分别索引为0,1 第五行,意思是将本地变量0(i)入栈 第六行是重点,dup指令复制栈顶元素 并入栈 实质就是生成了一个临时变量temp,temp=i

第7行 将数值1入栈 第8行 将栈顶两个元素相加 并将结果入栈 最后结果就是 栈顶元素值为1 栈顶之后的第二个元素(就是刚刚复制的那个i)0,注意这是很重要的一点,因为最后的赋值涉及到出栈顺序 第9行 栈顶元素出栈 赋值给索引为0的变量 也就是i,这个时候i=1,而我们的栈顶元素现在则是原始的i值0,第10行,栈顶元素出栈,赋值给索引为1的变量,也就是j,所以最后 j=0

再来看j=++i的情况

反汇编的MSIL代码 

.method public hidebysig static void  Main(string[] arg) cil managed
{
  .entrypoint
  // 代码大小       32 (0x20)
  .maxstack  2
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldc.i4.0
  IL_0004:  stloc.1
  IL_0005:  ldloc.0
  IL_0006:  ldc.i4.1
  IL_0007:  add
  IL_0008:  dup
  IL_0009:  stloc.0
  IL_000a:  stloc.1
  IL_000b:  ldloc.0
  IL_000c:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0011:  nop
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0018:  nop
  IL_0019:  call       string [mscorlib]System.Console::ReadLine()
  IL_001e:  pop
  IL_001f:  ret
} // end of method VimeTest::Main

看上去和i++的情况差不多 唯一不同的是dup指令所在的位置 dup指令在第8行而add在第7行 也就意味着栈顶的复制是发生在相加之后的 这样 最后出栈的时候两个值均为1 也就是i=j=1


通过上面的分析 大致就理清楚了i++和++i真正的区别在什么地方 对于.net来说 执行自增操作的时候 会将需要自增的值复制一份入栈 而++在前还是在后则决定了这个复制是发生在自增之前还是之后 如果是i++ 则会导致复制出来的值+1而原始值没有+1 最后出栈的顺序导致了一个为自增的值而另一个为没有自增的值 反之 对于++i 由于复制操作发生在相加操作之后 所以栈顶两个元素都为自增后的值 也就使得i和j都为1


原创粉丝点击