C# '|';vs'||';C语言中的编译器优化#

C# '|';vs'||';C语言中的编译器优化#,c#,.net,optimization,compiler-construction,bitwise-or,C#,.net,Optimization,Compiler Construction,Bitwise Or,我最近在一次采访中被问到这个问题,我完全错了,但我对C#和.net中的编译器优化感到好奇 考虑以下代码段: void Main() { Console.WriteLine("Results when bitwise or is used: "); Console.WriteLine(FuncA() | FuncB()); Console.WriteLine("Results when or operator is used: "); Console.WriteL

我最近在一次采访中被问到这个问题,我完全错了,但我对C#和.net中的编译器优化感到好奇

考虑以下代码段:

void Main()
{
    Console.WriteLine("Results when bitwise or is used: ");
    Console.WriteLine(FuncA() | FuncB());

    Console.WriteLine("Results when or operator is used: ");
    Console.WriteLine(FuncA() || FuncB()); 
}

bool FuncA()
{
    Console.WriteLine("Function A is executing.");
    return true;
}

bool FuncB()
{
    Console.WriteLine("Function B is executing.");
    return false;
}
运行上述结果会得到以下结果:

使用按位or时的结果:

函数A正在执行

函数B正在执行

真的

使用or运算符时的结果:

函数A正在执行

真的


我这里的问题是,为什么编译器在使用位运算符时没有进行优化?就像对于C#or运算符,编译器已经知道了参数,那么为什么它不以同样的方式处理按位or呢

编辑:更新术语以与ECMA-334(C语言规范)保持一致。参考(如§14.10)是ECMA-334的特定章节

逻辑运算符(
|
&
^
)不在C中(§14.10)。这意味着即使表达式的最终结果由操作数的子集唯一确定,也将对所有操作数求值。语言规范中没有明确说明这些运算符不是短路的事实,但签名的表示方式暗示了这一点。对这些运算符的操作数进行求值时,就像在方法调用中传递操作数一样,即从左到右,而不是短路

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
另一方面,条件逻辑运算符(
|
&&
)是短路(§14.11)


该行为在语言规范(标准)中定义,可能是因为其与早期编程语言(C++,Java)的一致性而被选择的。

这不是优化-它是
&&
|
操作符的基本属性,称为短路

短路背后的理论是,左操作数的某些值意味着我们不必测试右操作数。使用
&&&
,当左侧为false时,表达式作为一个整体不可能为true,因此计算右侧没有意义。对于
|
,如果左侧为真,则右侧不相关


位运算符
|
&
^
不会短路-必须计算它们的两个操作数才能确定结果。

位运算符不会优化,因为它在需要结果的整个宽度时使用。本质上,它被定义为不被优化

相反,定义OR运算符是为了确定任一操作数是否为真


另一种考虑是OR运算符(>)是<强>布尔-< /强>运算符,它是可优化的,而位运算符(<)是强>不< /强>布尔运算符,因此不能短路。

运营商和
&
| |
使用提前退出策略,专门防止右项的副作用

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
Wikipedia 280Z28页链接的这个(稍加修改的)C示例显示了一个有用的示例:

int denom = 0;
if (denom && num/denom) {
    do_something();
}
在这种情况下,尽早退出术语评估意味着程序在评估条件时不会抛出除零错误,这是一件好事

相反,语言规范定义了位运算符,以在对其执行操作之前评估这两个术语,包括所有副作用

一个考虑这一点的方法是考虑比特运算符为<强> EXPRESS < /强>和逻辑运算符为<强>懒惰>强>关于它们的评价。渴望操作符将在继续之前评估它们的所有操作数,懒惰操作符按顺序考虑操作数,如果可能的话尽早退出。


顺便说一句,(假设的)
^
运算符不能在不计算两个操作数的情况下退出,因为结果不能由一个操作数的特定值确定,就像
&
|

检查语言规范的情况一样。。。这不是一个真正的问题,因为该代码中没有涉及优化,但明确指定了行为(对
|
短路,对
|
两侧都必须进行计算)。有一秒钟,我认为C/C++中确实存在冗余的
^
运算符。。因为嘿,这不是语言中最奇怪的事情;是的,我知道。但它应该在那里:PHeck,如果这是C++14的一个补充,我不会感到惊讶;)严格地说,如果|的左操作数都是1位的,那么编译器就不必计算另一端,但它需要计算,因为这是语言定义的方式。