函数内部的局部变量*实际上*何时被分配

When does a local variable inside a function *actually* gets allocated

只是好奇这个。以下是同一功能的两个代码片段:

void MyFunc1()
{
    int i = 10;
    object obj = null;

    if(something) return;
}

另一个是...

void MyFunc1()
{
    if(something) return;

    int i = 10;
    object obj = null;
}

现在第二个是否具有在 某事 为真时不分配变量的好处?或者局部堆栈变量(在当前范围内)总是在函数被调用时立即分配并且将 return 语句移动到顶部没有效果?

A link to dotnetperls.com article"When you call a method in your C# program, the runtime allocates a separate memory region to store all the local variable slots. This memory is allocated on the stack even if you do not access the variables in the function call."

已更新
下面是这两个函数的 IL 代码的比较。 Func2 指的是第二个片段。似乎这两种情况下的变量都是在开始时分配的,但在 Func2() 的情况下,它们是稍后初始化的。所以我想没有任何好处。

对于您的程序当您运行它时,确定这种情况何时发生的唯一方法是当您 运行 您的程序 时,请查看 JIT 编译器发出的代码 。 None 我们甚至可以权威地回答特定问题(好吧,我想编写 CLR 的人可以,只要他们知道您使用的是哪个版本的 CLR,以及可能的关于配置和您的实际情况的一些其他细节程序代码)。

局部变量在堆栈上的任何分配都严格"implementation detail"。而且 CLS 没有向我们承诺任何具体的实现。

一些本地人从不本身就在堆栈上结束,通常是因为存储在寄存器中,但运行使用时间是合法的堆space,只要它保留本地变量的正常生命周期语义。

另见 Eric Lippert 的精彩系列The Stack Is An Implementation Detail

Peter Duniho 的回答是正确的。我想提请注意你问题中更基本的问题:

does the second one have the benefit of NOT allocating the variables when something is true?

为什么这应该是一个好处?您的假设是为局部变量分配 space 具有 成本 ,不这样做具有 收益 ,并且此收益在某种程度上值得获得。分析局部变量的实际成本非常非常困难;有条件地避免分配有明显好处的假设是没有根据的。

解决您的具体问题:

The local stack variables (in current scope) are always allocated as soon as the function is called and moving the return statement to the top has no effect?

这么复杂的问题我没法轻易回答。让我们把它分解成更简单的问题:

Variables are storage locations. What are the lifetimes of the storage locations associated with local variables?

"ordinary" 局部变量的存储位置——以及 lambda 表达式、方法等的形式参数——具有短的、可预测的生命周期。 None 其中 方法进入之前, none 其中 方法终止之后,或者正常或异常。 C# 语言规范明确指出,允许局部变量生命周期在 运行 时比您想象的 ,如果这样做不会导致对单个-线程程序。

"unusual" 局部变量的存储位置——lambda 的外部变量、迭代器块中的局部变量、异步方法中的局部变量等等——具有难以在编译时分析的生命周期或在 运行 时间,因此被移动到垃圾收集堆,该堆使用 GC 策略来确定变量的生命周期。不需要清理这些变量ever;它们的存储寿命可以根据 C# 编译器的突发奇想或 运行 时间任意 延长。

Can a local which is unused be optimized away entirely?

是的。如果 C# 编译器或 运行time 可以确定从程序中完全删除 local 在单线程程序中没有明显的效果,那么它可能会随心所欲地这样做。从本质上讲,这是将其寿命缩短为零。

How are storage locations for "ordinary" locals allocated?

这是一个实现细节,但通常有两种技术。要么space在堆栈上保留,要么本地注册。

How does the runtime determine whether a local is enregistered or put on the stack?

这是抖动优化器的实现细节。有很多因素,比如:

  • 本地地址是否可以被盗用;寄存器没有地址
  • local是否作为参数传递给另一个方法
  • local是否为当前方法的参数
  • 涉及的所有方法的调用约定是什么
  • 本地大小
  • 还有很多很多因素

Suppose we consider only the ordinary locals which are put on the stack. Is it the case that storage locations for all such locals are allocated when a method is entered?

同样,这是一个实现细节,但通常答案是肯定的。

So a "stack local" that is used conditionally would not be allocated off the stack conditionally? Rather, its stack location would always be allocated.

通常是的。

What are the performance tradeoffs inherent in that decision?

假设我们有两个locals,A和B,一个有条件使用,一个无条件使用。哪个更快:

  • 向当前堆栈指针添加两个单元
  • 将两个新的堆栈槽初始化为零

  • 向当前堆栈指针添加一个单元
  • 将新堆栈槽初始化为零
  • 如果满足条件,则将当前栈指针加一单元,并将新的栈槽初始化为零

请记住 "add one" 和 "add two" 的费用相同。

如果变量 B 未使用,此方案不会便宜,如果 是,则成本 两倍 已使用。那不是胜利。

But what about space? The conditional scheme uses either one or two units of stack space but the unconditional scheme uses two regardless.

正确。堆栈 space 很便宜。或者,更准确地说,每个线程获得的百万字节堆栈 space 是 非常昂贵的 ,并且该费用是预先支付的 ,当你分配线程时。大多数程序从不使用接近一百万字节的堆栈 space;试图优化 space 的使用就像花一个小时来决定是花 5.01 美元买一杯拿铁咖啡,还是花 5.02 美元买一杯拿铁咖啡,而当你银行里有一百万美元时;不值得。

Suppose 100% of the stack-based locals are allocated conditionally. Could the jitter put the addition to the stack pointer after the conditional code?

理论上是的。抖动是否真正实现了这种优化——一种实际上节省了不到十亿分之一秒的优化——我不知道。请记住,任何决定保存十亿分之一秒的抖动 运行 代码都是需要花费远远超过十亿分之一秒的代码。再说一次,花几个小时担心几分钱是没有意义的;时间就是金钱。

当然,将您节省的十亿分之一秒作为通用路径的现实性如何?大多数方法调用做一些事情,而不是return立即

此外,请记住,对于所有未注册的临时值槽,堆栈指针必须移动,无论这些槽是否具有names 与否。判断方法returns 自身是否没有接触栈的子表达式的情况有多少?因为 那是 你实际提出的条件得到了优化。这似乎是一组微乎其微的场景,您在其中获得的收益微乎其微。如果我正在编写一个优化器,我会把我宝贵时间的零百分比花在解决这个问题上,而我可以优化的是更容易获得的、唾手可得的果实场景。

Suppose there are two locals that are each allocated conditionally under different conditions. Are there additional costs imposed by a conditional allocation scheme other than possibly doing two stack pointer moves instead of one or zero?

是的。在将堆栈指针移动两个槽并说 "stack pointer is A, stack pointer + 1 is B" 的直接方案中,您现在拥有一种贯穿整个方法的一致方法来表征变量 A 和 B。如果您有条件地移动堆栈指针,那么有时堆栈指针是A,有时是B,有时两者都不是。这使 使用 A 和 B 的所有代码变得非常复杂。

What if the locals are enregistered?

那么这就成了寄存器调度的问题;我建议您参考有关该主题的大量文献。我远不是这方面的专家。