如果你有这个代码:
void Main()
{
string name;
}
然后,当使用编译器优化编译(在 LINQPad 中)时,您将获得以下 IL:
IL_0000: 重新
并且没有优化:
IL_0000:没有
IL_0001: 重新
没有为此声明分配内存 - 只是一个 NOP 操作作为 IL 中未优化代码的占位符。
当你的程序是这样的:
void Main()
{
string name = "Jack";
}
那么你编译优化的代码是:
IL_0000: 重新
编译器只是忽略未使用的变量。
未优化的代码会生成这个:
IL_0000:没有
IL_0001: ldstr "杰克"
IL_0006: stloc.0 // 名称
IL_0007: 恢复
显然未优化的代码更具解释性,所以从现在开始我只会展示未优化的代码,除非我明确说明。
现在让我们让代码做一些更有趣的事情。
void Main()
{
string name = "Jack";
Console.WriteLine(name);
}
这会产生:
IL_0000:没有
IL_0001: ldstr "杰克"
IL_0006: stloc.0 // 名称
IL_0007: ldloc.0 // 名称
IL_0008:调用 System.Console.WriteLine
IL_000D:没有
IL_000E: 恢复
有趣的是,如果我们把这段代码改成这样:
void Main()
{
int answer = 42;
Console.WriteLine(answer);
}
我们得到这个:
IL_0000:没有
IL_0001:ldc.i4.s 2A
IL_0003: stloc.0 // 回答
IL_0004: ldloc.0 // 回答
IL_0005:调用 System.Console.WriteLine
IL_000A:没有
IL_000B: 恢复
代码与string 示例几乎相同。
ldstr 调用正在获取对字符串字面量的引用(存储在大对象堆(不是小对象堆的普通堆)上的字符串池中)并将其推送到评估堆栈。
ldc.i4.s 正在将数字 42 的引用推送到评估堆栈。
然后,在这两种情况下,stloc.0 将评估堆栈顶部的值存储到方法的第零个本地内存位置。
然后,在这两种情况下,ldloc.0 再次从第零个本地内存位置加载值并将其放入评估堆栈。
您可能可以想象编译器在优化这段代码时会做什么。
System.Console.WriteLine 终于完成了。
现在让我们更详细地看看那个讨厌的string 代码。
我说它存储在实习生池中。让我们检查一下。
获取此代码:
void Main()
{
string name = "Jack";
Console.WriteLine(String.IsInterned(name));
}
它产生:
IL_0000:没有
IL_0001: ldstr "杰克"
IL_0006: stloc.0 // 名称
IL_0007: ldloc.0 // 名称
IL_0008:调用 System.String.IsInterned
IL_000D:调用 System.Console.WriteLine
IL_0012:没有
IL_0013: 重新
它会将Jack 输出到控制台。只有在System.String.IsInterned 返回一个内部字符串时,它才能做到这一点。
拿这个程序来展示反面:
void Main()
{
string name = String.Join("", new [] { "Ja", "ck" });
Console.WriteLine(String.IsInterned(name));
}
它将null 推送到控制台 - 这意味着字符串name 没有被保留,因此在这种情况下name 存储在堆(小对象堆)上。
让我们看看你的第二段代码:
void Main()
{
for (int i = 0; i < 20; i++)
{
Run();
}
}
private void Run()
{
int age = 20;
}
如果我们查看优化的 IL,那么 Run 方法如下所示:
跑步:
IL_0000: 重新
未优化的 IL 是这样的:
跑步:
IL_0000:没有
IL_0001:ldc.i4.s 14
IL_0003: stloc.0 // 年龄
IL_0004: 恢复
并且,就像我之前使用int 的示例一样,它将文字值20(或十六进制的14)加载到评估堆栈中,然后立即将其存储在该方法的本地内存中,并且然后返回。因此,对于局部变量age,它会重复使用相同的内存 20 次。