C# 这个循环有可能失败吗?

C# 这个循环有可能失败吗?,c#,variable-assignment,language-specifications,C#,Variable Assignment,Language Specifications,最近出现了一个问题,这对我来说是一次学习经历。类似于以下内容的内容给出了“使用未定义的”错误: 但我以前并没有意识到,依赖于某个变量值的内部作用域块会出错,即使无法察觉变量可能“错误”: 使用编译时常量解决此问题很好: int a; if(true) a = 2; a /= 2; //fine 我想知道这是否是因为编译器完全删除了if,但更复杂的语句也可以: int a; for(int i = 0; true; i++){ a = 2; if(i >= 10) br

最近出现了一个问题,这对我来说是一次学习经历。类似于以下内容的内容给出了“使用未定义的”错误:

但我以前并没有意识到,依赖于某个变量值的内部作用域块会出错,即使无法察觉变量可能“错误”:

使用编译时常量解决此问题很好:

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
也许这也被内联/优化了,但我问题的实质是,对于(int i=0;i<1;i++)的第一个简单循环
,是否有任何可能的方式使循环不运行,因此“变量a可能未分配”是一个有效的断言,或者静态流分析只是在一个简单的循环上运行“设置变量
a
的任何有条件控制的代码块都会自动被视为有一种情况,即它可能无法运行,我们可以直接在后续使用中显示错误”规则

实际上是否有任何可能的方式使循环不会运行,因此“变量a可能未分配”是一个有效的断言

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

我要指出的是,您的最后一个示例不是一个优化案例。它的工作原理与您已经建立的
while(true)
案例类似,它允许编译器将变量视为明确指定的

就“为什么”而言,有两种方法可以解释这个问题。简单的方法是“编译器为什么这样做?”而答案是“因为语言规范这么说”

语言规范并不总是最容易阅读的,明确赋值的规则就是这种说法的一个特别明显的例子,但是你可以在这里找到对“为什么”的第一种解释的答案:

您会注意到,通常情况下,循环控制结构导致确定赋值的唯一方式是控制循环本身的表达式是否参与确定赋值。这会影响“在真表达式之后确定赋值”和“在假表达式之后确定赋值”“次国家情景。您还将注意到规范的这一部分不适用于您的示例

因此,您只剩下循环的确定赋值规则的要点(还有其他限制条件,但没有一个适用于简单情况):

v在expr的开头与stmt的开头具有相同的确定赋值状态

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

所以,如果循环通常不创建明确的赋值,为什么由文字值(即“常量表达式”)控制的循环允许明确的赋值?这是因为规范的不同部分,由明确分配规则引用:

流分析考虑控制语句行为的常量表达式()的值,但不考虑非常量表达式的可能值

进行流分析是为了确定语句或循环端点的可达性,但这直接适用于确定的赋值:

int a;
if(someboolean)
  a=2;
else
  a=4;
  • 在块的端点处v的确定赋值状态,
    已检查
    未检查
    如果
    执行
    为每个
    锁定
    使用
    ,或
    switch
    语句是通过检查以该语句的端点为目标的所有控制流传输上的确定赋值状态v来确定的。如果在所有此类控制流传输上都明确指定了v,则在语句的端点处明确指定了v。否则;在语句的结尾处不一定指定v可能的控制流传输集的确定方法与检查语句可达性的方法相同[强调]
换言之,编译器在确定明确的赋值时,将应用与语句可达性相同的分析。因此,由常量表达式控制的循环将得到分析,而由非常量表达式控制的循环则不会得到分析

解释“为什么”更难的方法是“语言作者为什么以这种方式编写规范?”这就是你开始讨论基于观点的答案的地方,除非你真的在与语言作者之一交谈(事实上,他可能在某个时候发布了答案,所以……并非完全不可能:))

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

  • 他们可能是这样编写规范的,因为尽管现在的确定赋值规则非常复杂,但是如果编译器被要求对变量进行静态流分析,它们会变得更加复杂,更不用说实际编写编译器会有多复杂
  • 从理论上讲,这可以归结为停顿问题。也就是说,一旦你开始要求编译器进行非平凡的流分析,你就为某人编写一些C代码打开了大门,这有效地让编译器确定C代码是否可以停止。因为这在所有情况下都不可能做到,所以在规范中包含该需求可能是个坏主意
处理常量表达式是一回事,它不仅可以而且必须在编译时计算。让编译器运行你的程序只是为了编译它,这完全是另一回事。

问题的形式是“编译器为什么这样做?”
int a;
if(true)
  a = 2;
a /= 2; //fine
int a;
for(int i = 0; true; i++){
  a = 2;
  if(i >= 10)
    break;
}
a /= 2; //fine