web-dev-qa-db-ja.com

C ++では、なぜtrue && true || false && false == true?

コンパイラが次のコードを解釈する方法を誰かが知っているかどうか知りたいのですが。

#include <iostream>
using namespace std;

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

&&の優先順位は||よりも高いため、これは本当ですか?または||短絡演算子は何ですか(言い換えると、短絡演算子は後続のすべての式を無視しますか、それとも次の式だけを無視しますか)?

16
Andrew

Caladain 正解ですが、彼の回答に対するあなたのコメントの1つに返信したいと思います。

||を短絡した場合演算子が発生し、2番目の&&式の実行を短絡します。つまり、||演算子は、2番目の&&演算子の前に実行されました。これは、&&および||の左から右への実行を意味します。 (&&優先順位ではありません)。

あなたが抱えている問題の一部は、優先順位があなたがそれが何を意味すると思うかを完全に意味しないということだと思います。 _&&_の優先順位が_||_よりも高いのは事実であり、これは、表示されている動作を正確に説明しています。通常の算術演算子の場合を考えてみましょう。a * b + c * (d + e)があるとします。優先順位が示すのは、括弧を挿入する方法です。最初は_*_の周り、次に_+_の周りです。これにより、_(a * b) + (c * (d + e))_が得られます。あなたの場合、_(1 && 1) || (infiniteLoop() && infiniteLoop())_があります。次に、式がtreesになることを想像してください。これを行うには、各演算子を、2つの引数を子として持つノードに変換します。

Expression trees.

このツリーを評価すると、短絡が発生します。算術ツリーでは、幅優先のボトムアップ実行スタイルを想像できます。最初に_DE = d + e_を評価し、次に_AB = a * b_と_CDE = c * DE_を評価します。最終結果は_AB + CDE_です。ただし、等しく最初にABを評価し、次にDECDE、そして最終結果を評価できたことに注意してください。違いはわかりません。ただし、_||_と_&&_は短絡しているため、この左端から最初の評価を使用する必要があります。したがって、_||_を評価するには、最初に_1 && 1_を評価します。これが当てはまるため、_||_は短絡し、右側の分岐を無視します。ただし、ifを評価した場合でも、infiniteLoop() && infiniteLoop()を評価する必要があります。最初。

それが役立つ場合は、ツリー内の各ノードを関数呼び出しと考えることができます。これにより、最初のケースではplus(times(a,b), times(c,plus(d,e)))、2番目のケースではor(and(1,1), and(infiniteLoop(),infiniteLoop())の表現が生成されます。短絡とは、左側の各関数の引数をorまたはandに完全に評価する必要があることを意味します。 trueorの場合)またはfalseandの場合)の場合は、右側の引数を無視します。

あなたのコメントは、優先順位が最も高いものすべてを最初に評価し、次に優先順位が最も高いものすべてを評価することを前提としています。これは、ツリーの幅優先のボトムアップ実行に対応します。代わりに、優先順位がツリーの構築方法を教えてくれます。ツリーの実行規則は、単純な算術の場合には関係ありません。ただし、短絡は、ツリーの評価方法の正確な仕様です。


編集1:他のコメントの1つで、あなたは言った

あなたの算術の例では、最後の加算の前に2つの乗算を評価する必要がありますが、それが優先順位を定義するものではありませんか?

はい、これが優先順位を定義するものです。ただし、それが完全に真実ではない場合を除きます。 [〜#〜] c [〜#〜]では確かに正確ですが、(C以外の!)式_0 * 5 ^ 7_をどのように評価するかを検討してください。ヘッド。ここで、_5 ^ 7 = 57_および_^_は_*_よりも優先されます。幅優先のボトムアップルールに従って、結果を見つける前に_0_と_5 ^ 7_を評価する必要があります。しかし、_5 ^ 7_をわざわざ評価する必要はありません。 「まあ、すべてのxに対して_0 * x = 0_なので、これは_0_でなければなりません」と言って、右側のブランチ全体をスキップします。言い換えれば、私は最終的な乗算を評価する前に両側を完全に評価していません。短絡しました。同様に、_false && _ == false_と_true || _ == true_は___であるため、右側に触れる必要がない場合があります。これは、オペレーターが短絡していることの意味です。 Cは乗算を短絡しません(言語はこれを行うことができますが)が、は_&&_と_||_を短絡します

_0 * 5 ^ 7_を短絡しても、通常のPEMDAS優先順位規則が変更されないのと同様に、論理演算子を短絡しても、_&&_の優先順位が_||_よりも高いという事実は変わりません。それは単なるショートカットです。最初に評価する演算子のsome側を選択する必要があるため、Cは論理演算子の左側を最初に評価することを約束します。これが行われると、特定の値について右側を評価することを回避するための明白な(そして有用な)方法があり、Cはこれを行うことを約束します。

あなたのルール(表現の幅優先ボトムアップを評価する)も明確に定義されており、言語はこれを行うことを選択できます。ただし、短絡を許可しないという欠点があり、これは便利な動作です。ただし、ツリー内のすべての式が明確に定義されていて(ループがなく)、純粋である(変数の変更や印刷などがない)場合、違いはわかりません。 「and」と「or」の数学的定義がカバーしていないこれらの奇妙な場合にのみ、短絡が目に見えることさえあります。

また、左端の式を優先して短絡が機能するという事実については、基本的なは何もないことに注意してください。言語Ɔを定義できます。ここで、_⅋⅋_はandを表し、_\\_は_||_を表しますが、0 ⅋⅋ infiniteLoop()1 \\ infiniteLoop()はループし、infiniteLoop() ⅋⅋ 0infiniteLoop() \\ 1はfalseとtrueになります。 、それぞれ。これは、左側ではなく右側を最初に評価し、次に同じ方法で単純化することを選択することに対応します。

一言で言えば:優先順位が示すのは、解析ツリーの構築方法です。解析ツリーを評価するための唯一の賢明な順序は、あたかも幅優先のボトムアップで評価するかのように動作する順序です(= /// =)明確に定義された純粋な値。未定義または不純な値の場合、some線形順序を選択する必要があります。1 線形順序が選択されると、演算子の片側の特定の値によって、式全体の結果が一意に決定される場合があります(eg、_0 * _ == _ * 0 == 0_、_false && _ == _ && false == false_ 、または_true || _ == _ || true == true_)。このため、その後の線形順序での評価を完了せずに逃げることができる場合があります。 Cは、論理演算子_&&_および_||_を左から右に評価することでこれを実行し、他の目的では実行しないことを約束します。ただし、優先順位のおかげで、doは_true || true && false_がtrueではなくfalseであることを知っています。

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

の代わりに

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

1:実際には、演算子の両側を並行して理論的に評価することもできますが、それは今のところ重要ではなく、確かに意味がありません。 C.これにより、より柔軟なセマンティクスが生まれますが、副作用に問題があります(いつ発生しますか?)。

29

&&||よりも優先されます。

34
AngelLeliel

(true && true || false && false)は、&&の優先順位が高く評価されます。

TRUE && TRUE = True

FALSE && FALSE = False

True || False = True

更新:

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

なぜこれがC++で真になるのですか?

前のように、それを分解しましょう。 &&の優先順位は||よりも高くなりますおよびブールステートメントは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;

右から左に行くのか、左から右に行くのか思い出せませんが、どちらにしても結果は同じです。 2回目の投稿で、||より高い優先順位を持っていた:

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

tl; dr: &&が最初に評価されるのは依然として優先順位ですが、コンパイラはORも短絡します。本質的に、コンパイラは次の順序をグループ化します。このような操作(SIMPLISTIC VIEW、putdownピッチフォーク:-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)

pdate#2: "ただし、||は2番目の&&の前に評価されるため、この例では&&が優先されないことがわかります。これは、&&と||の優先順位が等しく、左側で評価されることを示しています-右の順序。」

「&&が優先された場合、最初の&&(1)、次に2番目の&&(無限ループ)が評価され、プログラムがハングします。これは発生しないため、&&は||の前に評価されません。」

これらについて詳しく説明しましょう。

ここでは、2つの異なることについて話します。演算の順序を決定する優先順位と、プロセッササイクルを節約するためのコンパイラ/言語のトリックである短絡。

最初に優先順位について説明しましょう。優先順位は「操作の順序」の省略形です。本質的に、次のステートメントが与えられます。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を考えてみましょう。エイダでは、「AND」と「OR」は「熱心な」演算子です。これらはすべてを強制的に評価します。

Ada(true AND true)OR(false AND false)は、ORを評価する前に(true AND true)と(false AND false)の両方を評価します。Adaは、短絡する機能も提供します。 ANDTHENおよびOR ELSEを使用すると、C++と同じ動作が得られます。

それがあなたの質問に完全に答えることを願っています。そうでない場合は、私に知らせてください:-)

更新3:最後の更新です。それでも問題が解決しない場合は、引き続きメールでお知らせします。

「||演算子の短絡が発生し、2番目の&&式の実行が短絡した場合、||演算子が2番目の&&演算子の前に実行されたことを意味します。これは、&&および||の左から右への実行を意味します( &&優先順位ではありません)。」

次に、この例を見てみましょう。

(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は最初の2つでヒットするでしょう。また、3番目の例でも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)

Precendeceは、操作のグループ化のみを設定します。この場合、&&は||よりも大きくなります。

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

コンパイラThenがやって来て、サイクルを節約するための最良の機会を与えるために、評価を実行する順序を決定します。

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())

それは一種の事実です..そして私はこれを実証する他の方法がわかりません。優先順位は、言語の文法規則によって決定されます。コンパイラの最適化は各コンパイラによって決定されます。一部は他よりも優れていますが、Allは、グループ化された比較を自由に並べ替えることができます。比較を最小限に抑えて最速で実行するための「最良の」チャンスを与えます。

22
Caladain

2つの事実が、両方の例の動作を説明しています。まず、_&&_の優先順位は_||_よりも高くなります。次に、両方の論理演算子が短絡評価を使用します。

優先順位は、評価の順序と混同されることがよくありますが、独立しています。式は、最終結果が正しい限り、個々の要素を任意の順序で評価できます。一般に、一部の演算子では、演算子自体が適用される前に両方が評価される限り、左側の値(LHS)が右側の値(RHS)の前または後に評価される可能性があることを意味します。

論理演算子には特別なプロパティがあります。特定の場合、一方の側が特定の値に評価されると、もう一方の側の値に関係なく、演算子の値がわかります。このプロパティを便利にするために、C言語(および拡張によりすべてのCのような言語)は、RHSの前にLHSを評価し、さらにその値が必須の場合にのみRHSを評価する論理演算子を指定しました=演算子の結果を知るため。

したがって、TRUEFALSEの通常の定義を想定すると、_TRUE && TRUE || FALSE && FALSE_は左から評価されます。最初のTRUEは最初の_&&_の結果を強制しないため、2番目のTRUEが評価され、次に式_TRUE && TRUE_が(TRUEに)評価されます。 。これで、_||_はそのLHSを認識します。さらに良いことに、そのLHSは_||_の結果を強制的に認識させたため、RHS全体の評価をスキップします。

2番目のケースでは、まったく同じ評価順序が適用されます。 _||_のRHSは無関係であるため、評価されず、infiniteLoop()の呼び出しも行われません。

この動作は仕様によるものであり、便利です。たとえば、式がNULLポインターの逆参照を決して試みないことを知って、_p && p->next_を書くことができます。

7
RBerteig

&&は確かに より高い優先順位 を持っています。

「||演算子の短絡が発生し、2番目の&&式の実行が短絡した場合、||演算子が2番目の&&演算子の前に実行されたことを意味します。これは、&&および||の左から右への実行を意味します( &&優先順位ではありません)。」

完全ではありません。

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

このように評価されます:

  1. 最初のオペレーターを確認してください。
  2. xx && yyを評価する
  3. 次の演算子を確認してください。
  4. 次の演算子が||で、ステートメントの最初の部分がtrueの場合、残りをスキップします。
  5. それ以外の場合は、||の次の演算子を確認し、それを評価します:zz && qq
  6. 最後に、||を評価します。

私の理解では、C++は、評価を開始する前に物事を読み込むように設計されています。結局のところ、この例では、&&の後に2回目の||チェックがあることを、それが読み込まれるまでわかりません。つまり、その前に||を読み込む必要があります。 2番目の&&に到達します。そのため、最初の部分がtrueと評価された場合、||の後の部分は実行されませんが、最初の部分がfalseと評価された場合、最初の部分が実行され、||、2番目の部分を見つけて評価し、2番目の部分の結果を使用して最終結果を決定します。

3
Anonymoose

true && true || infiniteLoop() && infiniteLoop()の例については、2つの特性が組み合わされているため、どちらの無限ループ呼び出しも評価されていません。&&は||および||よりも優先されます。左側が真の場合に短絡します。

&&と||の場合同じ優先順位があった場合、評価は次のように行う必要があります。

((( 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 )
1
filipe

アンドリューの最新のコードに関して、

#include <iostream>
using namespace std;

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

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

短絡評価とは、infiniteLoopへの呼び出しが実行されないことが保証されていることを意味します。

ただし、C++ 0xドラフトでは、無限ループが何もしない未定義の振る舞いになるため、ひねくれた方法で興味深いものになります。その新しいルールは、一般的に非常に望ましくなく、愚かで、まったく危険でさえあると見なされていますが、ドラフトに潜入しているようなものです。ある論文の著者が、それが何かまたは他のかなり無関係なもののルールを単純化すると考えたスレッドシナリオの考慮から部分的に。

したがって、C++ 0x適合性の「最先端」にあるコンパイラを使用すると、実行された場合でも、someの結果でプログラムが終了する可能性があります。 infiniteLoopへの呼び出し!もちろん、そのようなコンパイラを使用すると、その恐ろしい現象、鼻のデーモンを生成することもできます...

幸いなことに、前述のように、短絡評価とは、呼び出しが実行されないことが保証されていることを意味します。

乾杯&hth。、

編集に関して:trueであるため、infiniteLoop()は評価されません|| (何でも)常に真実です。 trueを使用する| (何でも)何でも実行する必要がある場合。

1
Philipp

And/or/true/falseは*/+/1/0に非常に似ているため(数学的に同等です)、次のことも当てはまります。

1 * 1 + 0 * 0 == 1

覚えやすいです...

そうです、それは優先順位と関係がありますそして短絡。ブール演算の優先順位は、対応する整数演算にマップするとかなり簡単です。

1
Macke

これは連続した図です:

  (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を返すだけです。

0
nonopolarity

簡単な答えは、&&が||よりも優先されるということです。さらに、ブール式の結果を知る必要がないため、コードは実行されません。はい、それはコンパイラの最適化です。

0
Carl