昨天在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);
}
{
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
{
.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,至于为什么,即使我们不反汇编也应该明白,肯定是出栈或入栈的顺序导致的,有兴趣的朋友不妨去试着分析一下:)