一句C#代码的分析.

题记:本文纯粹是出于研究的目的,实际上下面的那种写法是根本不被提倡,也不会有人使用的.代码的优劣在于保证性能和程序扩展性的情况下,本身表述清晰与否,一味的追求简化是没有任何好处的,除了让你自己觉得很cool.当然,读你代码的人绝对不会那么认为.


昨天在CSDN首页看到一个帖子,问,i=0;i=i++;最后得出的i等于多少?
答案很容易得出,Console.WriteLine(i)可以看到是0.

答案当然不是我们所关注的,为什么会是0呢?如果是i=0;j=i++;那任何人都能得出j=0这个结果,但是这里是给i自己赋值,但是i++这个自增操作又确实执行了,那最后为什么会是i=0呢?隐约觉得应该是出栈顺序造成的结果,猜测是没有意义的,就让我们看看IL代码到底是怎么回事.

这是一段简单的C#代码:

 static void Main(string[] args)
        
{
            
int i = 0;
            i 
= i++;
            Console.WriteLine(i);
        }



对应的IL代码如下:
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  
// Code size       17 (0x11)
  .maxstack  
3
  .locals init (
[0] int32 i)
  IL_0000:  nop             
  IL_0001:  ldc.i4.
0   
  IL_0002:  stloc.
0    
  IL_0003:  ldloc.
0   
  IL_0004:  dup       
  IL_0005:  ldc.i4.
1  
  IL_0006:  
add          
  IL_0007:  stloc.
0   
  IL_0008:  stloc.
0   
  IL_0009:  ldloc.
0 
  IL_000a:  call       void 
[mscorlib]System.Console::WriteLine(int32)
  IL_000f:  nop
  IL_0010:  ret
// end of method Program::Main


下面让我们分析一下这段IL代码.
到IL_003为止,这里实现了一次数据的入栈操作,即把变量v0的值push到了ES寄存器里.对应的C#代码是int i = 0;
此时的栈情况如下:
[0] //此时v0=0

然后执行了dup操作,复制栈顶元素,这里对应的操作我们可以理解为是i = i++;中的右边i的出现.
此时的栈的情况如下:
[0,0] //v0=0

接下来是在ES中再申请4byte的空间,值为1.注意,这里并未把值赋给变量,因为这正是常量的申明.
栈情况如下:
[v0,0,1] //v0=0
这里的1的申明实际上是i++操作引起的.单独的i++等价i+=1;如果我们查看IL的话,也会发现两者是一模一样的

随后就进入了关键的部分:操作和出栈.
add执行的加操作,需要两个参数,因为结果依然是在ES中存储中,因此操作完后,栈的情况如下:
[0,0+1]
到现在,已经很清楚了,接下来的两次出栈做的是同一件事,弹出栈顶元素,赋给v0.如下:
[0,0+1]-->pop 1 to v0,v0 = 1;
[0]-->pop 0 to v0,v0 = 0;
v0正是最终i的值,为0.

疑点:
为何最后会有两次出栈操作?
答案是,在i = i++;中实际上有两部操作,i = 右边(未知数);i = i + 1;因此,必然存在两次出栈.

最后,实际上在C/C++中i=i++后,得出的结果为1,至于为什么,即使我们不反汇编也应该明白,肯定是出栈或入栈的顺序导致的,有兴趣的朋友不妨去试着分析一下:)

posted on 2006-09-12 09:13  wiseman  阅读(3514)  评论(12编辑  收藏  举报

导航