【问题标题】:What happens when creating a variable? [closed]创建变量时会发生什么? [关闭]
【发布时间】:2018-08-13 12:23:49
【问题描述】:
void Method1()
{
string str = 
client.GetString("http://msdn.microsoft.com");

}

Method1 的第一行执行后到底发生了什么?

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

【问题讨论】:

  • @Alejandro:作业的右侧。
  • 如果 RHS 没有被执行,那还有什么意义呢?
  • 是的。这是声明 (string str) 和初始化 (str = ...) 在一个语句中。
  • 代码不是逐行运行的,尤其是在实际代码单元的中间切行时。因此,很难在这里准确地说出您想知道什么以及回答什么。只有一行调用一个方法并将结果存储在一个变量中。编译器可能会抛出整个变量赋值,因为它没有在任何地方使用,因此可能没有任何内存分配。
  • “是否值得存储一个变量,然后再对该变量进行处理”——很可能; 完全取决于上下文,尽管

标签: c# variables


【解决方案1】:

这在很大程度上取决于您接下来要做什么。如果您不使用 str完全除非您在下一步,或者你从现在到那时所做的事情就堆栈位置而言是“净零”)。当然,它仍然会执行对client.GetString(...) 的调用;问题是它对结果有什么作用?编译器可以通过多种方式解释它:

  • 作为本地人:

本地的堆栈空间作为堆栈帧条目的一部分保留;在调用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

【讨论】:

  • 对不起,我不知道你说了什么,但我会保存回复,希望有一天我能回到它并能够理解它:) 谢谢马克,我也保存了新的编辑
  • @jimbob 我添加了更多可能对您有所帮助的上下文...... JIT 编译器然后可以做更多的巫术;在sharplab.io 上还有一个“JIT Asm”标签
  • @jimbob 最终,没有办法回答这个不涉及堆栈帧和 IL...
  • 您在程序集列表中似乎犯了一个复制和粘贴错误:Foo.Method3_LocalFoo.Method2_LeftOnStack 相同。
【解决方案2】:

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

注意你提到了

Method1第一行执行时

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

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

正如 Marc Gravell 所指出的,事情可能会变得更加复杂。编译器可能决定根本不创建变量,或者以不同的方式安排事物,只要产生相同的结果(称为compiler optimizations)。这个答案的其余部分假设编译器没有优化任何东西并创建了一个与给定代码完全匹配的二进制文件,但在发布版本中我们可以预料到一些差异。

*(未被 lambda 捕获)

【讨论】:

  • 注意 - 很有可能(很可能,甚至)这里实际上没有保留堆栈空间;许多局部变量被编译器完全省略
  • @MarcGravell 当然,在编译器优化变量的情况下(可能在这种情况下确实如此)。为简单起见,我假设编译器按问题中的立场完全编译代码,不涉及任何优化。在现实生活中,实际的二进制文件可能与代码有很大不同,只要它产生相同的结果。
  • 一个可能的混淆来源:编译器如何在方法开始时保留内存,而它甚至不知道字符串的长度?指出字符串 content 的空间在 GetString() 调用中的某处分配,而 Method1 只存储一个 reference (以避免丑陋单词“指针”)指向该空间,引用所需的存储量始终相同,与字符串长度无关。
【解决方案3】:

是的,在这种情况下,分配会立即发生。稍后您将了解一些有趣的事情,例如延迟加载、异步和委托,然后您将看到变量没有立即初始化的实例。不过不用担心,等你了解了这些东西,你就知道了,不会难懂。

恭喜你学习了世界上最美丽的编程语言。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-18
    • 1970-01-01
    • 2021-11-30
    相关资源
    最近更新 更多