为什么C#要提前绑定局部变量?

7

在C#中,您可能会有以下代码:

void DoSomething()
{
    //some code.
    int x = 5;
    //some more code.
}

一进入DoSomething,CLR就会为整数x设置空间。为什么它不等到int x = 5这行代码执行时才分配空间呢?特别是因为即使x被绑定了,它也不会让你在到达那行代码之前使用它。


8
逐步调整堆栈大小是否比一次性预留所有空间更好? - ildjarn
2
@L.B 这是一个具体的问题,很可能有一个具体的答案...为什么它不适合在 Stack Overflow 上提问? - GWLlosa
2
@GWLlosa:不,堆栈保留通常只是一个寄存器值的更改(在x86上为esp)-- 直到遇到new才会进行“分配”,这里所做的一切都是为了保留堆栈空间。 - ildjarn
3
好的,我会尽力进行翻译。关键词为“presumably”。请问有上下文或者需要翻译的句子吗?如果没有上下文或者具体翻译要求,请提供更多信息,以便我能够帮助您更好地完成翻译任务。 - L.B
3
根据常见问题解答中的内容:“您应该只提出基于实际问题的实用且可回答的问题。”你是否有具体的场景遇到这种问题呢? - M.Babcock
显示剩余11条评论
4个回答

13
一进入DoSomething方法,CLR就会为int x分配空间。为什么它不等到达带有int x = 5的那一行再分配呢?
这个问题无法回答,因为整个问题基于一个错误的前提条件。局部变量的存储空间可能是:
  • 在方法首次进入时分配
  • 在控制流达到声明时分配
  • 在控制流达到初始化时分配(假设声明和初始化不同)
  • 在特殊情况下分配,例如该局部变量是 lambda 的闭包变量、迭代器块中的变量或者 async 块中的变量时,局部变量的分配方式和时间可能会变得复杂
  • 完全省略;如果局部变量从未被使用,则可能首先不进行分配。
C# 编译器和 JIT 编译器确保局部变量的存储空间以一种正确的方式进行分配,并尝试确保它是高效的。它们选择如何进行分配取决于具体情况。预先分配空间可能更有效,只有当变量使用时才分配空间可能更有效;JIT 编译器在选择局部变量的生命周期方面具有广泛的自由度。如果 JIT 编译器可以在不违反程序正确性的情况下这样做,局部变量的生命周期可以比其作用域预示的更长或更短。
由于问题的前提条件是错误的,所以无法回答该问题。提出一个更好的问题。

答案是mu - jason
1
对于一个声称问题没有答案的回答,如果它获得了10个赞和被采纳,那么这个回答似乎在存在上就自相矛盾了。 - Igby Largeman
2
@IgbyLargeman:当你将鼠标悬停在赞同按钮上时,会出现“此回答有用”的提示,而不是“此回答回答了所提出的问题”。如果指出一个问题基于错误前提是有用的,那么这里就没有矛盾。 - Eric Lippert
1
我仍然认为你对时空结构的不稳定做出了一些贡献,即使只是一点点。当你的办公室里出现切斯特菲尔德沙发时,不要怪我,这就是我想说的。 - Igby Largeman
1
@IgbyLargeman:实际上我办公室已经有一张切斯特菲尔德沙发了。(而且我注意到你已经暴露出自己很可能和我一样是一个讲英语的加拿大人;世界上很少有人在加拿大英语地区之外将沙发称为“Chesterfields”。) - Eric Lippert
1
我有一种预感你会这么说。我打赌你的办公室很棒。我是一个讲英语的澳大利亚人(但生活在你们南边的一个州),但是切斯特菲尔德的参考来自道格拉斯·亚当斯。在澳大利亚,我们只叫它沙发。 - Igby Largeman

10

正如你所知,从C#代码到本地代码的步骤有几个:

  • 从C#编译为IL(字节码)
  • 从字节码JIT到本地代码

C#不能控制内存分配时间(称为binding),这完全由JIT决定。让我们看看C#能控制什么。C#产生的字节码必须遵循CLR ECMA标准。如果我们进入第一部分的12.1.6.1部分,我们会看到,该标准定义了局部变量的位置在方法头中。由于方法签名通常出现在列表中的开头,因此您会得出一个(错误的)印象,即它们是提前绑定的,而实际上可能是也可能不是。

但是,如果您查看编译后的本地代码,结果可能因平台而异。从历史上看,为本地变量在CPU堆栈上分配空间是通过改变堆栈指针的单个CPU指令完成的。如果要逐个变量进行,则需要使用许多指令,每个变量一个,效率较低。这就是为什么至少在x86上您会看到CPU堆栈上的空间是提前分配的原因。


3

如果编译器发现以下类似的内容,应该怎么处理:

 for (int i = 0; i < 1000; i++)
 {
     int j = .... //should the compiler set up space when it reaches this line? 1000 times?
 }

除此之外,我真的认为设置本地空间的成本不是一个因素。如果成本变得重要,那么您可能正在处理太多本地空间,在这种情况下,最好重新设计代码。

1

你的问题似乎基于一些假设:

  • 设置成本总是很高的
  • 设置只会发生少数几次
  • CLR级别的引用/值类型始终与C#级别的变量具有一对一的映射关系

这些可能对你的代码是正确的,但对大多数代码来说可能不成立。

这些假设还似乎忽略了编译/解释此过程的底层层次的存在,将其转换为机器代码。

简而言之,在C#中编写的代码是一个抽象,它依赖于另一个抽象IL,后者依赖于CLR,依此类推。

就我个人而言,我严重怀疑这个决定是否会对您的应用程序性能产生重大影响...但也许像Eric Lippert(http://blogs.msdn.com/b/ericlippert/)这样的人可以分享更深入的分析。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接