17

我想知道是否有人知道编译器解释以下代码的方式:

#include <iostream>
using namespace std;

int main() {
 cout << (true && true || false && false) << endl; // true
}

这是真的吗,因为 && 的优先级高于 || 或者因为 || 是短路运算符(换句话说,短路运算符是否忽略所有后续表达式,或仅忽略下一个表达式)?

4

12 回答 12

35

&&的优先级高于||

于 2010-10-31T05:08:14.837 回答
29

Caladain的答案完全正确,但我想回复您对他的回答的评论之一:

如果短路 || 运算符出现并使第二个 && 表达式的执行短路,这意味着 || 运算符在第二个 && 运算符之前执行。这意味着 && 和 || 从左到右执行 (不是 && 优先级)。

我认为您遇到的部分问题是优先级并不完全意味着您认为它的含义。确实&&比 具有更高的优先级||,这正是您所看到的行为的原因。考虑使用普通算术运算符的情况:假设我们有a * b + c * (d + e). 优先级告诉我们的是如何插入括号:首先是 around *,然后是 around +。这给了我们(a * b) + (c * (d + e)); 在您的情况下,我们有(1 && 1) || (infiniteLoop() && infiniteLoop()). 然后,想象表达式变成。为此,请将每个运算符转换为一个节点,并将其两个参数作为子节点:

表达式树。

评估这棵树是短路的用武之地。在算术树中,您可以想象一种广度优先自下而上的执行方式:首先评估DE = d + e,然后AB = a * bCDE = c * DE,最后的结果是AB + CDE。但请注意,您同样可以先评估AB,然后评估DECDE和最终结果;你无法区分。但是,由于||&&是短路的,它们必须使用这种最左优先的求值方式。因此,要评估||,我们首先评估1 && 1。因为这是真的,所以会||短路并忽略它的右手分支——即使已经评估了它,它也必须评估infiniteLoop() && infiniteLoop()第一的。

如果有帮助,您可以将树中的每个节点视为一个函数调用,它plus(times(a,b), times(c,plus(d,e)))在第一种情况和or(and(1,1), and(infiniteLoop(),infiniteLoop())第二种情况下产生以下表示。短路意味着您必须完全评估oror的每个左侧函数参数and;如果是true(for or) 或false(for and),则忽略右侧参数。

您的评论假定我们首先评估具有最高优先级的所有内容,然后是具有次高优先级的所有内容,依此类推,这对应于树的广度优先自下而上执行。相反,优先级告诉我们如何构建树。树的执行规则在简单的算术情况下是无关紧要的;然而,短路正是如何评估树的精确规范。


编辑1:在您的其他评论之一中,您说

您的算术示例需要在最终加法之前评估两次乘法,这不是定义优先级的原因吗?

是的,这就是优先级的定义——但它并不完全正确。在C中当然是完全正确的,但请考虑如何在脑海中评估(非 C!)表达式0 * 5 ^ 7,其中和的优先级高于. 根据您的广度优先自下而上规则,我们需要评估and才能找到结果。但你不会费心去评估;您只需说“好吧,因为对于所有人来说,这一定是”,然后跳过整个右手分支。换句话说,在评估最终乘法之前,我还没有完全评估双方;我短路了。同样,因为和对于任何5 ^ 7 = 57^*05 ^ 75 ^ 70 * x = 0x0false && _ == falsetrue || _ == true_,我们可能不需要触摸右手边;这就是操作员短路的意思。C 不会短路乘法(尽管一种语言可以做到这一点),但它短路&&||.

正如短路0 * 5 ^ 7不会改变通常的 PEMDAS 优先规则一样,短路逻辑运算符也不会改变&&优先级高于||. 这只是一个捷径。由于我们必须先选择运算符的一侧进行计算,因此 C 承诺首先计算逻辑运算符的左侧;一旦完成此操作,就有一种明显(且有用)的方法可以避免评估某些值的右侧,C 承诺会这样做。

你的规则——评估表达式广度优先自下而上——也是明确定义的,一种语言可以选择这样做。但是,它的缺点是不允许短路,这是一种有用的行为。但是如果你的树中的每个表达式都是明确定义的(没有循环)和纯粹的(没有修改变量、打印等),那么你就无法区分。只有在这些奇怪的情况下,“和”和“或”的数学定义没有涵盖,短路才是可见的。

另外,请注意,短路通过优先处理最左边的表达式来起作用这一事实并没有什么根本意义。可以定义一种语言Ɔ,其中⅋⅋表示and\\表示||,但在哪里0 ⅋⅋ infiniteLoop()1 \\ infiniteLoop()将循环,并且infiniteLoop() ⅋⅋ 0infiniteLoop() \\ 1分别为假和真。这仅对应于选择先评估右侧而不是左侧,然后以相同的方式进行简化。

简而言之:优先级告诉我们的是如何构建解析树。评估解析树的唯一合理顺序是那些表现得好像我们在定义明确的纯值上以广度优先自下而上(如您所愿)评估它的顺序。对于未定义或不纯的值,必须选择一些线性顺序。1 一旦选择了线性顺序,运算符一侧的某些值可能唯一地确定整个表达式的结果(例如0 * _ == _ * 0 == 0false && _ == _ && false == falsetrue || _ == _ || true == true)。因此,您可能无需完成对线性顺序之后出现的任何内容的评估就可以逃脱;C 承诺为逻辑运算符&&||通过以从左到右的方式评估它们,而不是为了其他任何事情。但是,由于优先级,我们确实知道这true || true && falsetrue和不是false:因为

  true || true && false
→ true || (true && false)
→ true || false
→ true

代替

  true || true && false
↛ (true || true) && false
→ true && false
→ false

1:实际上,理论上我们也可以并行计算运算符的两侧,但这现在并不重要,而且对 C 肯定没有意义。这产生了更灵活的语义,但它与 side- 存在问题影响(它们何时发生?)。

于 2010-11-01T03:15:44.993 回答
21

(true && true || false && false) 用 && 进行评估,具有更高的优先级。

TRUE && TRUE = True

FALSE && FALSE = False

True || False = True

更新:

1&&1||infiniteLoop()&&infiniteLoop()

为什么这在 C++ 中会产生 true ?

像以前一样,让我们​​把它分开。&& 的优先级高于 || 和 C++ 中的布尔语句短路。

1 && 1 = True.

当布尔值转换为整数值时,则

false -> 0
true -> 1

该表达式计算这个 (true) && (true) 语句,它使 || 短路,从而防止无限循环运行。有更多的编译器 Juju 正在进行,所以这是一个简单的情况视图,对于这个例子来说已经足够了。

在非短路环境中,该表达式将永远挂起,因为 OR 的两侧都将被“评估”而右侧将挂起。

如果您对优先级感到困惑,这就是如果 || 在原始帖子中的评估方式 优先级高于 &&:

1st.) True || False = True
2nd.) True && 1st = True
3rd.) 2nd && false = false
Expression = False;

我不记得它是从右到左还是从左到右,但无论哪种方式结果都是一样的。在你的第二篇文章中,如果 || 优先级更高:

1st.) 1||InfLoop();  Hang forever, but assuming it didn't
2nd.) 1 && 1st;
3rd.) 2nd && InfLoop(); Hang Forever

tl; dr:首先评估 && 仍然是优先级,但编译器也会使 OR 短路。本质上,编译器将这样的操作顺序分组(简单视图,放下干草叉:-P)

1st.) Is 1&&1 True?
2nd.) Evaluate if the Left side of the operation is true, 
      if so, skip the second test and return True,
      Otherwise return the value of the second test(this is the OR)
3rd.) Is Inf() && Inf() True? (this would hang forever since 
      you have an infinite loop)

更新 #2: “但是,此示例证明 && 没有优先级,因为 || 在第二个 && 之前进行评估。这表明 && 和 || 具有相同的优先级,并且按照从左到右的顺序进行评估。”

“如果 && 具有优先权,它将评估第一个 && (1),然后是第二个 &&(无限循环)并挂起程序。因为这不会发生,所以 && 在 || 之前不会被评估。”

让我们详细介绍这些。

我们在这里谈论两个不同的事情。优先级,它决定了操作的顺序,以及短路,这是一个编译器/语言技巧来节省处理器周期。

让我们先介绍优先级。优先级是“操作顺序”的简写 本质上,给定以下语句: 1 + 2 * 3 操作应按什么顺序分组以进行评估?

数学清楚地将运算顺序定义为给予乘法比加法更高的优先级。

1 + (2 * 3) = 1 + 2 * 3
2 * 3 is evaluated first, and then 1 is added to the result.
* has higher precedence than +, thus that operation is evaluated first.

现在,让我们转换为布尔表达式: (&& = AND, || = OR)

true AND false OR true

C++ 赋予 AND 比 OR 更高的优先级,因此

(true AND false) OR true
true AND false is evaluated first, and then 
      used as the left hand for the OR statement

因此,仅在优先级上, (true && true || false && false) 将按以下顺序操作:

((true && true) || (false && false)) = (true && true || false && false)
1st Comparison.) true && true
2nd Comparison.) false && false
3rd Comparison.) Result of 1st comparison || Result of Second

到目前为止和我在一起?现在让我们进入短路:在 C++ 中,布尔语句就是所谓的“短路”。这意味着编译器将查看给定语句并选择“最佳路径”进行评估。举个例子:

(true && true) || (false && false)
There is no need to evaluate the (false && false) if (true && true) 
equals true, since only one side of the OR statement needs to be true.
Thus, the compiler will Short Circuit the expression.  Here's the compiler's
Simplified logic:
1st.) Is (true && true) True?
2nd.) Evaluate if the Left side of the operation is true, 
      if so, skip the second test and return True,
      Otherwise return the value of the second test(this is the OR)
3rd.) Is (false && false) True? Return this value

如您所见,如果 (true && true) 被评估为 TRUE,则无需花费时钟周期来评估 (false && false) 是否为 true。

C++ 总是简短的 Circuts,但其他语言为所谓的“Eager”运算符提供了机制。

以编程语言 Ada 为例。在 Ada 中,“AND”和“OR”是“Eager”运算符……它们强制对所有内容进行评估。

在 Ada (true AND true) OR (false AND false) 中,会在评估 OR 之前同时评估 (true AND true) 和 (false AND false)。Ada 还使您能够使用 AND THEN 和 OR ELSE 进行短路,这将为您提供与 C++ 相同的行为。

我希望这能完全回答你的问题。如果没有,请告诉我:-)

更新 3: 上次更新,如果您仍有问题,我会继续发送电子邮件。

“如果短路 || 运算符并短路第二个 && 表达式的执行,这意味着 || 运算符在第二个 && 运算符之前执行。这意味着 && 和 || 从左到右执行 (不是 && 优先级)。”

让我们看一下这个例子:

(false && infLoop()) || (true && true) = true (Put a breakpoint in InfLoop and it won't get hit)
false && infLoop() || true && true = true  (Put a breakpoint in InfLoop and it won't get hit)
false || (false && true && infLoop()) || true = false (infLoop doesn't get hit)

如果你说的是真的,InfLoop 会在前两个中受到打击。您还会注意到 InfLoop() 在第三个示例中也没有被调用。

现在,让我们看看这个:

(false || true && infLoop() || true);

Infloop 被调用!如果 OR 的优先级高于 &&,则编译器将评估:

(false || true) && (infLoop() || true) = true;
(false || true) =true
(infLoop() || true = true (infLoop isn't called)

但是 InfLoop 被调用了!这就是为什么:

(false || true && infLoop() || true);
1st Comparison.) true && InfLoop() (InfLoop gets called)
2nd Comparison.) False || 1st Comp (will never get here)
3rd Comparison.) 2nd Comp || true; (will never get here)

Precence 仅设置操作的分组。在此,&& 大于 ||。

true && false || true && true gets grouped as
(true && false) || (true && true);

然后编译器出现并确定它应该执行评估的顺序,以便为它提供节省周期的最佳机会。

Consider: false && infLoop() || true && true
Precedence Grouping goes like this:
(false && infLoop()) || (true && true)
The compiler then looks at it, and decides it will order the execution in this order:
(true && true) THEN || THEN (false && InfLoop())

这是一个事实......我不知道如何证明这一点。优先级由语言语法规则决定。编译器的优化由每个编译器确定。有些编译器比其他编译器更好,但是所有编译器都可以自由地重新排序分组比较,因为它认为合适,以便以最少的比较给它“最好的”机会以最快的执行速度。

于 2010-10-31T05:13:36.530 回答
7

&&确实有更高的优先级

于 2010-10-31T05:08:23.097 回答
7

两个事实解释了这两个例子的行为。首先, 的优先级 &&高于||。其次,两个逻辑运算符都使用短路求值。

优先级经常与评估顺序混淆,但它是独立的。只要最终结果是正确的,表达式可以以任何顺序评估其各个元素。一般来说,对于某些运算符,这意味着左侧的值 (LHS) 可以在右侧的值 (RHS) 之前或之后进行评估,只要在应用运算符本身之前对两者进行评估即可。

逻辑运算符有一个特殊的属性:在某些情况下,如果一侧计算为特定值,则无论另一侧的值如何,运算符的值都是已知的。为了使这个属性有用,C 语言(以及每一种类似 C 的语言)已经指定了逻辑运算符来在 RHS 之前评估 LHS,并且进一步仅在需要其值来知道 RHS 的结果时才评估RHS操作员。

因此,假设 和 的通常定义TRUEFALSE左侧TRUE && TRUE || FALSE && FALSE开始计算。第一个TRUE不强制第一个的结果&&,所以第二个TRUE被评估,然后表达式TRUE && TRUE被评估(为真)。现在,||知道它的 LHS。更妙的是,它的 LHS 迫使其结果||为人所知,因此它跳过了对其整个 RHS 的评估。

完全相同的评估顺序适用于第二种情况。由于 的 RHS ||无关紧要,因此不会对其进行评估,也不会调用infiniteLoop()

此行为是设计使然,并且很有用。例如,您可以在编写时p && p->next知道表达式永远不会尝试取消引用 NULL 指针。

于 2010-11-01T02:23:13.487 回答
3

“如果短路 || 运算符并短路第二个 && 表达式的执行,这意味着 || 运算符在第二个 && 运算符之前执行。这意味着 && 和 || 从左到右执行 (不是 && 优先级)。”

不完全的。

(xx && yy || zz && qq)

会被这样评价:

  1. 检查第一个操作员。
  2. 评估xx && yy
  3. 检查下一个运算符。
  4. 如果下一个运算符是||,并且语句的第一部分为真,则跳过其余部分。
  5. 否则,在 之后检查下一个运算符||,并对其进行评估:zz && qq
  6. 最后,评价||

据我了解,C++ 的设计目的是在开始评估之前读取内容。毕竟,在我们的示例中,它不知道我们在读入它之前还有第二次&&检查||,这意味着它必须在读入||第二次之前读入&&。因此,如果第一部分评估为真,它不会执行之后的部分||,但如果第一部分评估为假,那么它将执行第一部分,读入||,查找并评估第二部分,并使用第二部分的结果来确定最终结果。

于 2012-12-06T20:37:15.797 回答
1

关于您的编辑:infiniteLoop() 不会被评估,因为 true || (无论如何)总是正确的。使用真 | (无论如何)如果应该执行任何事情。

于 2010-10-31T15:08:18.530 回答
1

关于这个true && true || infiniteLoop() && infiniteLoop()例子,由于结合了两个特征,没有一个无限循环调用被评估:&& 优先于 || 和 || 左侧为真时短路。

如果 && 和 || 具有相同的优先级,评估必须像这样:

((( true && true ) || infiniteLoop ) && infiniteLoop )
(( true || infiniteLoop ) && infiniteLoop )
=> first call to infiniteLoop is short-circuited
(true && infiniteLoop) => second call to infiniteLoop would have to be evaluated

但由于 && 的优先级,评估实际上是

(( true && true ) || ( infiniteLoop && infiniteLoop ))
( true || ( infiniteLoop && infiniteLoop ))
=> the entire ( infiniteLoop && infiniteLoop ) expression is short circuited
( true )
于 2010-10-31T17:54:51.950 回答
1

关于安德鲁的最新代码,

#include <iostream>
using namespace std;

bool infiniteLoop () {
    while (true);
    return false;
}

int main() {
    cout << (true && true || infiniteLoop() && infiniteLoop()) << endl; // true
}

短路评估意味着infiniteLoop保证不会执行对的调用。

然而,它以一种反常的方式很有趣,因为 C++0x 草案使无限循环无所事事Undefined Behavior。这条新规则通常被认为是非常不受欢迎和愚蠢的,甚至是彻头彻尾的危险,但它有点偷偷溜进了草案。部分是出于对线程场景的考虑,一篇论文的作者认为这会简化一些无关紧要的事情的规则。

因此,使用处于 C++0x 一致性“前沿”的编译器,即使它执行了infiniteLoop! 当然,有了这样的编译器,它也可以产生可怕的现象,鼻守护进程......

令人高兴的是,如上所述,短路评估意味着保证不会执行调用。

干杯&hth.,

于 2010-11-01T04:19:50.173 回答
1

由于 and/or/true/false 与 */+/1/0 非常相似(它们在数学上是等价的),因此以下也是正确的:

1 * 1 + 0 * 0 == 1

而且很容易记住...

所以是的,它与优先级短路有关。如果将布尔运算映射到其相应的整数运算,则布尔运算的优先级相当容易。

于 2010-11-04T19:06:06.870 回答
0

这是一个连续的插图:

  (true && true || false && false)
= ((true && true) || (false && false))  // because && is higher precedence than ||, 
                                        //   like 1 + 2 * 3 = 7  (* higher than +)
= ((true) || (false))
= true

但还要注意,如果是

(true || ( ... ))

那么右手边不会被评估,所以那里的任何函数都不会被调用,表达式只会返回true

于 2010-11-01T03:00:49.583 回答
0

简单的答案是 && 比 || 具有更高的优先级。此外,代码不会被执行,因为它不需要知道布尔表达式的结果。是的,它是编译器优化。

于 2010-11-01T03:41:40.250 回答