繁体   English   中英

C#中的无用变量用于循环反汇编和捕获委托?

[英]Useless variable in C# for loop disassembly with capturing delegate?

我试着看看这个旧问题中发布的代码的反汇编,我发现了一些奇怪的东西。

为清楚起见,这是源代码:

class ThreadTest
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
            new Thread(() => Console.WriteLine(i)).Start();
    }
}

(当然这个程序的行为是出乎意料的,这不是问题。)

这是我看到的反汇编:

internal class ThreadTest
{
    private static void Main(string[] args)
    {
        int i;
        int j;
        for (i = 0; i < 10; i = j + 1)
        {
            new Thread(delegate
            {
                Console.WriteLine(i);
            }).Start();
            j = i;
        }
    }
}

j在那做什么? 这是字节码:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 64 (0x40)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] class ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0' 'CS$<>8__locals0',
        [1] int32
    )

    IL_0000: newobj instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: ldc.i4.0
    IL_0008: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
    IL_000d: br.s IL_0035
    // loop start (head: IL_0035)
        IL_000f: ldloc.0
        IL_0010: ldftn instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::'<Main>b__0'()
        IL_0016: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object, native int)
        IL_001b: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
        IL_0020: call instance void [mscorlib]System.Threading.Thread::Start()
        IL_0025: ldloc.0
        IL_0026: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
        IL_002b: ldc.i4.1
        IL_002c: add
        IL_002d: stloc.1
        IL_002e: ldloc.0
        IL_002f: ldloc.1
        IL_0030: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i

        IL_0035: ldloc.0
        IL_0036: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
        IL_003b: ldc.i4.s 10
        IL_003d: blt.s IL_000f
    // end loop

    IL_003f: ret
} // end of method ThreadTest::Main

但这是最奇怪的事情。 如果我像这样更改原始代码,用i = i + 1替换i++

class ThreadTest
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i = i + 1)
            new Thread(() => Console.WriteLine(i)).Start();
    }
}

我明白了:

internal class ThreadTest
{
    private static void Main(string[] args)
    {
        int i;
        for (i = 0; i < 10; i++)
        {
            new Thread(delegate
            {
                Console.WriteLine(i);
            }).Start();
        }
    }
}

这正是我的预期。

这是字节码:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 62 (0x3e)
    .maxstack 3
    .entrypoint
    .locals init (
        [0] class ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0' 'CS$<>8__locals0'
    )

    IL_0000: newobj instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: ldc.i4.0
    IL_0008: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
    IL_000d: br.s IL_0033
    // loop start (head: IL_0033)
        IL_000f: ldloc.0
        IL_0010: ldftn instance void ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::'<Main>b__0'()
        IL_0016: newobj instance void [mscorlib]System.Threading.ThreadStart::.ctor(object, native int)
        IL_001b: newobj instance void [mscorlib]System.Threading.Thread::.ctor(class [mscorlib]System.Threading.ThreadStart)
        IL_0020: call instance void [mscorlib]System.Threading.Thread::Start()
        IL_0025: ldloc.0
        IL_0026: ldloc.0
        IL_0027: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
        IL_002c: ldc.i4.1
        IL_002d: add
        IL_002e: stfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i

        IL_0033: ldloc.0
        IL_0034: ldfld int32 ConsoleApplication2.ThreadTest/'<>c__DisplayClass0_0'::i
        IL_0039: ldc.i4.s 10
        IL_003b: blt.s IL_000f
    // end loop

    IL_003d: ret
} // end of method ThreadTest::Main

为什么编译器在第一个场景中添加j

注意:我正在使用VS 2015 Update 3,.NET Framework 4.5.2,在发布模式下进行编译。

因为在语义上,当你编写i++ ,编译器需要保留i的原始值,以便它可以用作表达式的结果值。

编译器通过引入一个新变量来实现这一点,在该变量中可以保留新值,直到使用i的旧值为止。 因此,旧值i仍然可以被读取,直到更新的j值被复制到i 当然,在这种情况下,在将add指令的结果复制到j之后立即发生,因为实际上没有代码确实需要该值。 但是,有一段时间i的价值仍然是旧的,如果需要它就可以使用。

你可能会说:

但是,我从不使用这个价值。 为什么编译器会保留它? 为什么不直接将add的结果写入i而不是先将它存储在j呢?

C#编译器不负责优化。 它的主要工作是将C#代码转换为IL。 事实上,我会说这工作的一部分是工作很辛苦,优化的东西,而是跟随执行的常见模式,使事情的JIT编译器,它负责优化更容易。

通过不包括优化这种退化场景的逻辑,更容易确保C#编译器生成正确的IL,并以可预测,更易于优化的方式执行此操作。

i++并不完全是i = i + 1因为你也可以这样做:

试试这段代码:

int i = 1;
int x = 5 + i++;
Console.WriteLine("i:" + i + " x: " + x);
i = 1;
int y = 5 + ++i;
Console.WriteLine("i:" + i + " y: " + y);

输出:

i:2 x: 6
i:2 y: 7

这与前缀和后缀增量/减量有关(请参阅前缀(++ x)和后缀(x ++)操作如何工作? )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM