这个循环有没有可能失败到 运行?

Is it ever possible for this loop to fail to run?

最近出现了一个问题,对我来说是一次学习经历。类似下面的内容给出了 "use of undefined" 错误:

int a;
for(int i = 0; i < 1; i++)
  a = 2;
a /= 2;

这是一个人为的例子,没有意义,但它给出了所需的错误。我知道使用内部范围来设置变量值是完全可以的,只要编译器可以计算出所有流程都会导致明确的分配:

int a;
if(someboolean)
  a=2;
else
  a=4;

但我以前没有意识到取决于某些变量值的内部作用域块会出错,即使没有明显的变量可能 "wrong":

int a;
bool alwaysTrue = true;
if(alwaysTrue)
  a = 2;
a /= 2; //error

用编译时常量解决这个问题就可以了:

int a;
if(true)
  a = 2;
a /= 2; //fine

我想知道这是否可能是因为编译器完全删除了 if,但更复杂的语句也可以:

int a;
for(int i = 0; true; i++){
  a = 2;
  if(i >= 10)
    break;
}
a /= 2; //fine

也许这也是 inlined/optimised,但我的问题的本质是,对于第一个简单循环 for(int i = 0; i < 1; i++) 实际上有任何可能的方式使循环不会 运行因此 "variable a may be unassigned" 是一个有效的断言,或者静态流分析只是 运行 简单的 "any conditionally controlled code block that sets variable a is automatically deemed to have a situation where it might not run and we short cut straight to showing an error on the subsequent use" 规则?

is there actually any conceivable way that the loop will NOT run and hence the "variable a may be unassigned" is a valid assertion

在你的例子中,假设a是一个局部变量,循环必须运行。除了在实例化它们的线程中外,不能修改局部变量。只是编译器不需要确定是这种情况,也不会。

我要指出,您的最后一个示例不是优化案例。它的工作方式就像您已经建立的 while (true) 情况一样,允许编译器将变量视为已明确分配。

就"why"而言,有两种解释该问题的方法。简单的方法是 "why does the compiler do this?" 答案是 "because the language specification says so".

语言规范并不总是最容易阅读的,明确赋值的规则是该陈述的一个特别明显的例子,但您可以在这里找到 "why" 的第一个解释的答案: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/variables#precise-rules-for-determining-definite-assignment

您会注意到,通常情况下,循环控制结构导致明确赋值的唯一方式是控制循环本身的表达式是否参与明确赋值。这符合 "Definitely assigned after true expression""Definitely assigned after false expression" 子状态场景。您还会注意到规范的这一部分不适用于您的示例。

所以你只剩下循环的明确赋值规则的要点(还有其他条件,但none适用于简单情况):

v has the same definite assignment state at the beginning of expr as at the beginning of stmt.

即无论 v 在循环之前是什么,循环之后都是一样的。循环本身被忽略。

那么,如果循环通常不创建明确的赋值,为什么由文字值控制的循环(即 "constant expressions")允许明确的赋值?这是因为规范的不同部分,由明确分配的规则引用:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#end-points-and-reachability

The flow analysis takes into account the values of constant expressions (Constant expressions) that control the behavior of statements, but the possible values of non-constant expressions are not considered.

进行流分析以确定语句或循环终点的可达性,但这直接适用于明确分配:

  • The definite assignment state of v at the end point of a block, checked, unchecked, if, while, do, for, foreach, lock, using, or switch statement is determined by checking the definite assignment state of v on all control flow transfers that target the end point of that statement. If v is definitely assigned on all such control flow transfers, then v is definitely assigned at the end point of the statement. Otherwise; v is not definitely assigned at the end point of the statement. The set of possible control flow transfers is determined in the same way as for checking statement reachability [emphasis mine]

换句话说,编译器在确定明确赋值时将应用与语句可达性相同的分析。因此,由常量表达式控制的循环会被分析,而那些不是常量表达式的循环则不会。

更难解释 "why" 的方法是 "why did the language authors write the specification this way?" 这就是您开始进入基于意见的答案的地方,除非您实际上是在与其中一位语言作者交谈(实际上他可能在某个时候 post 一个答案,所以......并非遥不可及:))。

但是,在我看来,有几种方法可以解决这个问题:

  • 他们可能是这样写规范的,因为,就像现在的明确赋值规则一样复杂,如果编译器需要对变量进行静态流分析,它们会更加复杂,更不用说更多了实际上编写 编译器 会很复杂。
  • 从理论上讲,它归结为停机问题。 IE。一旦您开始要求编译器进行非平凡的流分析,您就为某人编写一些 C# 代码打开了大门,有效地使编译器确定 C# 代码是否可以暂停。由于不可能在所有情况下都做到这一点,因此在规范中包含该要求可能不是一个好主意。

处理常量表达式,它不仅可以而且必须在编译时计算是一回事。使编译器本质上 运行 你的程序只是为了编译它,是一个完整的 'nother ball o' 蜡。