void Method1()
{
string str =
client.GetString("http://msdn.microsoft.com");
}
执行Method1的第一行时会发生什么?
我知道为字符串变量str
预留了内存,但是语句的右侧是否也在此阶段执行?即它实际上是否检索到右侧的值?
答案 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)
是的,在这种情况下,分配立即进行。稍后,您将了解诸如延迟加载,异步和委托之类的有趣事情,然后您将看到未立即初始化变量的实例。但请放心,当您了解这些内容时,您将足够了解它,将不难理解。
并祝贺您学习世界上最美丽的编程语言。