创建变量时会发生什么?

时间:2018-08-13 12:23:49

标签: c# variables

void Method1()
{
string str = 
client.GetString("http://msdn.microsoft.com");

}

执行Method1的第一行时会发生什么?

我知道为字符串变量str预留了内存,但是语句的右侧是否也在此阶段执行?即它实际上是否检索到右侧的值?

3 个答案:

答案 0 :(得分:13)

这很大程度上取决于您下一步要做的事情。如果您不使用{em>除非,否则非常很有可能编译器实际上会完全删除str 在下一步中,或者您现在之间所做的事情在堆栈位置方面“净零”。当然,它将仍然执行对client.GetString(...)的调用;问题是它对结果有什么作用?编译器可以通过多种方式来解释这一点:

  • 作为本地人:

本地的堆栈空间被保留为stackframe条目的一部分;在调用GetString之后,编译器会发出stloc(或变体)

  • 作为环境堆栈值

没有为本地保留显式堆栈空间;在GetString()之后,只需将其留在下一个操作要消耗的位置即可(例如,如果紧跟Console.WriteLine(str);之类的静态调用,这将是完美的);如果需要多次,也可以将其克隆(dup

  • 弹出

没有为本地保留显式堆栈空间;在GetString()之后,它只是被删除(pop

  • 作为字段

这将适用于迭代器块和异步方法;解释很复杂

最终,如果您真的想知道,您需要查看真正的代码,然后查看IL-理想情况下是在“发布”模式下编译的。

您可以在this test code on sharplab.io

中看到其中的一些示例

或复制到此处:

void Method1_Popped()
{
    string str = client.GetString("http://msdn.microsoft.com");
}
void Method2_LeftOnStack()
{
    string str = client.GetString("http://msdn.microsoft.com");
    Console.WriteLine(str);
}
void Method3_Local()
{
    string str = client.GetString("http://msdn.microsoft.com");
    for(int i = 0;i < 3 ; i++) DoSomethingElse();
    Console.WriteLine(str);
}

成为:

.method private hidebysig 
    instance void Method1_Popped () cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 18 (0x12)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldfld class SomeClient Foo::client
    IL_0006: ldstr "http://msdn.microsoft.com"
    IL_000b: callvirt instance string SomeClient::GetString(string)
    IL_0010: pop
    IL_0011: ret
} // end of method Foo::Method1_Popped

.method private hidebysig 
    instance void Method2_LeftOnStack () cil managed 
{
    // Method begins at RVA 0x2063
    // Code size 22 (0x16)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldfld class SomeClient Foo::client
    IL_0006: ldstr "http://msdn.microsoft.com"
    IL_000b: callvirt instance string SomeClient::GetString(string)
    IL_0010: call void [mscorlib]System.Console::WriteLine(string)
    IL_0015: ret
} // end of method Foo::Method2_LeftOnStack

.method private hidebysig 
    instance void Method3_Local () cil managed 
{
    // Method begins at RVA 0x207c
    // Code size 42 (0x2a)
    .maxstack 2
    .locals init (
        [0] string,
        [1] int32
    )

    IL_0000: ldarg.0
    IL_0001: ldfld class SomeClient Foo::client
    IL_0006: ldstr "http://msdn.microsoft.com"
    IL_000b: callvirt instance string SomeClient::GetString(string)
    IL_0010: stloc.0
    IL_0011: ldc.i4.0
    IL_0012: stloc.1
    // sequence point: hidden
    IL_0013: br.s IL_001f
    // loop start (head: IL_001f)
        IL_0015: ldarg.0
        IL_0016: call instance void Foo::DoSomethingElse()
        IL_001b: ldloc.1
        IL_001c: ldc.i4.1
        IL_001d: add
        IL_001e: stloc.1

        IL_001f: ldloc.1
        IL_0020: ldc.i4.3
        IL_0021: blt.s IL_0015
    // end loop

    IL_0023: ldloc.0
    IL_0024: call void [mscorlib]System.Console::WriteLine(string)
    IL_0029: ret
} // end of method Foo::Method3_Local

或作为ASM:

Foo.Method1_Popped()
    L0000: mov ecx, [ecx+0x4]
    L0003: mov edx, [0xe42586c]
    L0009: cmp [ecx], ecx
    L000b: call dword [0x2ef71758]
    L0011: ret

Foo.Method2_LeftOnStack()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: mov ecx, [ecx+0x4]
    L0006: mov edx, [0xe42586c]
    L000c: cmp [ecx], ecx
    L000e: call dword [0x2ef71758]
    L0014: mov ecx, eax
    L0016: call System.Console.WriteLine(System.String)
    L001b: pop ebp
    L001c: ret

Foo.Method3_Local()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: mov ecx, [ecx+0x4]
    L0006: mov edx, [0xe42586c]
    L000c: cmp [ecx], ecx
    L000e: call dword [0x2ef71758]
    L0014: mov ecx, eax
    L0016: call System.Console.WriteLine(System.String)
    L001b: pop ebp
    L001c: ret

答案 1 :(得分:1)

内存分配取决于变量的使用方式以及声明位置。在这种情况下,作为方法*中的局部变量,一旦调用该方法(而不是在执行到达其声明的位置时),就将保留内存,而不管其后会发生什么。因此,即使根本不调用client.GetString("http://msdn.microsoft.com"),也可以保留内存(这里根本不可能发生,但是有可能使用更复杂的代码)。

请注意,您提到的

  

执行方法1的第一行时

此方法只有一行,其中包括声明一个变量并通过调用另一个方法为其分配值。您将其编写为两条物理行这一事实无关紧要,从逻辑上讲,您的整个代码仅包含一个步骤。同样,变量“声明”和分配在调用该方法后立即发生,其余部分在执行到该点时发生。

该行的执行实际上有两个阶段:第一,调用GetString方法。其次,将其返回值分配给局部变量。

正如Marc Gravell指出的那样,事情可能变得更加复杂。编译器可能会决定根本不创建该变量,或者仅对变量进行不同的布置,只要产生相同的结果(称为compiler optimizations)即可。该答案的其余部分假定编译器没有进行任何优化,而是创建了一个与给定代码完全完全匹配的二进制文件,但是在发行版本中,我们可以预期会有一些差异。

*(并且未被lambda捕获)

答案 2 :(得分:0)

是的,在这种情况下,分配立即进行。稍后,您将了解诸如延迟加载,异步和委托之类的有趣事情,然后您将看到未立即初始化变量的实例。但请放心,当您了解这些内容时,您将足够了解它,将不难理解。

并祝贺您学习世界上最美丽的编程语言。