Max*_*ich 7 c# compiler-construction inference compiler-optimization
如果你向Eric Lippert 转发大约13分钟的视频,他描述了对C#编译器所做的更改,该更改使得以下代码无效(显然在此代码编译之前包括.NET 2).
int y;
int x = 10;
if (x * 0 == 0)
y = 123;
Console.Write(y);
Run Code Online (Sandbox Code Playgroud)
现在我明白了上面代码的任何执行实际上都是为了评估
int y;
int x = 10;
y = 123;
Console.Write(y);
Run Code Online (Sandbox Code Playgroud)
但是我不明白为什么将下面的代码编译成"可编辑"被认为是"可取的"?IE:允许这样的推论运行的风险是什么?
我仍然觉得这个问题有点令人困惑,但让我看看我是否可以将问题改写成我能回答的形式.首先,让我重新陈述问题的背景:
在C#2.0中,此代码:
int x = 123;
int y;
if (x * 0 == 0)
y = 345;
Console.WriteLine(y);
Run Code Online (Sandbox Code Playgroud)
被视为你写的
int x = 123;
int y;
if (true)
y = 345;
Console.WriteLine(y);
Run Code Online (Sandbox Code Playgroud)
反过来被视为:
int x = 123;
int y;
y = 345;
Console.WriteLine(y);
Run Code Online (Sandbox Code Playgroud)
这是一个合法的计划.
但在C#3.0中,我们采取了突破性的改变来防止这种情况发生.尽管你和我都知道它总是正确的,但编译器不再将条件视为"始终为真".我们现在将其设置为非法程序,因为编译器认为它不知道 "if"的主体总是被执行,因此不知道局部变量y在使用之前总是被赋值.
为什么C#3.0行为正确?
这是正确的,因为规范声明:
常量表达式必须只包含常量.x * 0 == 0不是一个常量表达式,因为它包含一个非常数项,x.
if如果条件是等于的常量表达式,则只知道a 的结果总是可以到达true.
因此,给出的代码不应该将条件语句的结果分类为始终可访问,因此不应将本地分类y为明确分配.
为什么常量表达式只包含常量?
我们希望C#语言能够被用户清楚地理解,并且可以由编译器编写者正确实现.要求编译器对表达式的值进行所有可能的逻辑演绎,这些都是针对这些目标的.确定给定表达式是否为常量应该很简单,如果是,则其值是什么.简而言之,常量评估代码应该知道如何执行算术,但不应该知道关于算术操作的事实.常量赋值器知道如何乘以2*1,但它不需要知道"1是整数上的乘法同一性" 这一事实.
现在,编译器编写者可能会决定他们可以聪明的区域,从而生成更优的代码.允许编译器编写者这样做,但不能改变代码是合法的还是非法的.只有在给定合法代码时,才允许它们进行优化,使编译器的输出更好.
如何在C#2.0中发生错误?
发生的事情是编写器是为了过早地运行算术优化器而编写的.优化器应该是聪明的,它应该在程序被确定为合法之后运行.它在程序被确定为合法之前运行,因此影响了结果.
这是一个潜在的突破性变化:虽然它使编译器符合规范,但它也可能将工作代码转换为错误代码.是什么推动了变革?
LINQ功能,特别是表达式树.如果你说的话:
(int x)=>x * 0 == 0
Run Code Online (Sandbox Code Playgroud)
并将其转换为表达式树,您希望生成表达式树吗?
(int x)=>true
Run Code Online (Sandbox Code Playgroud)
?可能不是!您可能希望它生成"将x乘以零并将结果与零进行比较"的表达式树. 表达式树应该保留体内表达式的逻辑结构.
当我编写表达式树代码时,尚不清楚设计委员会是否会决定是否
()=>2 + 3
Run Code Online (Sandbox Code Playgroud)
将生成"添加二到三"的表达式树或"五"的表达式树.我们决定对后者-常量都生成表达式树前折叠,但算术应该产生表达式树之前无法通过优化运行.
那么,现在让我们考虑一下我们刚才所说的依赖关系:
我们必须找到一个命令来完成所有这些工作,尊重所有这些依赖.C#2.0中的编译器按此顺序执行:
表达树重写在哪里?无处!显然这是错误的,因为流量分析现在考虑了算术优化器推导出的事实.我们决定重新编写编译器,以便它按顺序执行:
这显然需要改变.
现在,我确实考虑通过这样做来保留现有的破坏行为:
优化的算术表达式将包含指向其未优化形式的指针.为了保护错误,我们认为这太复杂了.我们决定最好修复bug,进行重大改变,并使编译器架构更容易理解.