短絡論理演算子は必須ですか?そして評価順は?


140

ANSI規格では、CまたはC ++のいずれかで、論理演算子を短絡させることを義務付けていますか?

あなたのコードは短絡されているこれらの演算に依存すべきではないと言っているK&Rの本を思い​​出して混乱しています。誰かが標準のどこで論理演算が常に短絡されていると言われているのか指摘してもらえますか?私は主にC ++に興味がありますが、Cの回答も素晴らしいでしょう。

私はまた、評価順序が厳密に定義されていないことを読んだことを思い出します(どこで思い出せないか)。したがって、コードは依存しないか、式内の関数が特定の順序で実行されると想定しないでください。呼び出されますが、コンパイラーは最も効率的な順序を自由に選択できます。

標準はこの式の評価順序を示していますか?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";

12
注意:PODタイプには当てはまります。しかし、演算子&&または演算子||をオーバーロードした場合 特定のクラスでは、これらは繰り返しではなく、ショートカットではありません。このため、独自のクラスに対してこれらの演算子を定義しないことをお勧めします。
マーティンヨーク

これらの演算子を少し前に再定義しました。基本的なブール代数演算を行うクラスを作成したときです。おそらく「これは短絡と左右の評価を破壊します!」という警告コメントを付けるべきです。私がこれを忘れた場合に備えて。* / +もオーバーロードし、それらの同義語にしました:-)
Joe Pineda

if-blockで関数呼び出しを行うことは、良いプログラミング方法ではありません。メソッドの戻り値を保持する変数を常に宣言し、ifブロックで使用します。
SRチャイタンヤ2017年

6
@SRChaitanya不正解です。ここで示したように、特に不適切なプラクティスとして任意に説明することは、特にブール値を返す関数では常に行われます。
ローン侯爵

回答:


154

はい、演算子||&&CおよびC ++標準の両方で、短絡と評価の順序が必要です。

C ++標準は次のように述べています(C標準には同等の句があるはずです)。

1.9.18

次の式の評価で

a && b
a || b
a ? b : c
a , b

これらの式で演算子の組み込みの意味を使用すると、最初の式(12)の評価の後にシーケンスポイントがあります

C ++には追加のトラップがあります。短絡は、演算子||とをオーバーロードする型には適用されませ&&

脚注12:この段落で示されている演算子は、5項で説明されている組み込み演算子です。これらの演算子の1つが有効なコンテキストでオーバーロードされている場合(第13節)、ユーザー定義の演算子関数を指定すると、式は関数の呼び出し、およびオペランドは、暗黙のシーケンスポイントの間にない引数リストを形成します

特別な要件がない限り、これらの演算子をC ++でオーバーロードすることは通常お勧めできません。あなたはそれを行うことができますが、特にこれらの演算子がこれらの演算子をオーバーロードする型でインスタンス化するテンプレートを介して間接的に使用される場合、他の人々のコードで予想される動作を壊すかもしれません。


3
短絡がオーバーロードされた論理演算に適用されないことを知りませんでした、それはテストです。標準またはソースへの参照を追加できますか?私はあなたを信用していません。これについてもっと知りたいだけです。
ジョーピネダ

4
ええ、それは論理的です。operator &&(a、b)の引数として機能します。何が起こるかを言うのはそれの実装です。
ヨハネスシャウブ-litb 2009年

10
litb:評価せずにbをoperator &&((a、b)に渡すことは不可能です。また、コンパイラは副作用がないことを保証できないため、bの評価を元に戻す方法はありません。
jmucchiello 2009年

2
これは悲しいです。演算子&&と||を再定義する必要があると思いました。そして、それらはまだ完全に確定的であり、コンパイラはそれを検出して評価を短絡させます。結局のところ、順序は無関係であり、副作用がないことを保証します!
ジョーピネダ

2
@Joe:ただし、演​​算子の戻り値と引数は、ブール値から別の値に変わる可能性があります。以前は、3つの値(「true」、「false」、「unknown」)を使用して「特別な」ロジックを実装していました。戻り値は確定的ですが、短絡動作は適切ではありません。
Alex B

70

短絡評価と評価の順序は、CとC ++の両方で必須の意味標準です。

そうでなければ、このようなコードは一般的なイディオムではありません

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

セクション6.5.13 C99仕様の論理AND演算子(PDFリンク)によると

(4)。ビットごとのバイナリ&演算子とは異なり、&&演算子は左から右への評価を保証します。最初のオペランドの評価の後にシーケンスポイントがあります。最初のオペランドが0と等しい場合、2番目のオペランドは評価されません。

同様に、セクション6.5.14の論理OR演算子

(4)ビットごととは異なる| 演算子、|| 演算子は左から右への評価を保証します。最初のオペランドの評価の後にシーケンスポイントがあります。最初のオペランドが0と等しくない場合、2番目のオペランドは評価されません。

同様の表現はC ++標準にもありますこのドラフトコピーのセクション5.14を確認してください。チェッカーが別の回答で注記しているように、&&または||をオーバーライドする場合、通常の関数呼び出しになるため、両方のオペランドを評価する必要があります。


ああ、探していたもの!OK、評価の順序短絡の両方がANSI-C 99に従って必須です!私はANSI-C ++の同等のリファレンスを参照してください。
ジョー・ピネダ

C ++標準への適切なフリーリンクを見つけるのは難しい。グーグルで見つけたドラフトコピーにリンクしている。
ポールディクソン

PODタイプに当てはまります。しかし、演算子&&または演算子||をオーバーロードすると これらはショートカットではありません。
マーティンヨーク

1
ええ、boolの場合、評価順序と短絡動作が常に保証されていることに注目するのは興味深いことです。2つの組み込み型のoperator &&をオーバーロードできないためです。異なる動作をさせるには、オペランドに少なくとも1つのユーザー定義タイプが必要です。
Johannes Schaub-litb 2009年

チェッカーとこの回答の両方を受け入れたいと思います。私は主にC ++に興味があるので、他のものも受け入れますが、これも素晴らしいことを認めなければなりません!どうもありがとうございました!
ジョー・ピネダ

19

はい、それが義務付けられています(評価順序と短絡の両方)。あなたの例では、すべての関数がtrueを返す場合、呼び出しの順序は厳密にfunctionA、次にfunctionB、次にfunctionCの順です。このように使用

if(ptr && ptr->value) { 
    ...
}

コンマ演算子についても同じです。

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

一つは、左と右のオペランドの間言い&&||,との第一及び第二/第三のオペランドの間の?:「配列点」である(条件演算子)。副作用はそのポイントの前に完全に評価されます。したがって、これは安全です:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

コンマ演算子は、物事を区切るために使用される構文コンマと混同しないように注意してください。

// order of calls to a and b is unspecified!
function(a(), b());

C ++標準では次のように述べてい5.14/1ます。

&&演算子は左から右にグループ化します。オペランドは両方とも暗黙的にbool型に変換されます(句4)。両方のオペランドがtrueの場合、結果はtrueになり、それ以外の場合はfalseになります。&とは異なり、&&は左から右への評価を保証します。最初のオペランドがfalseの場合、2番目のオペランドは評価されません。

そしてで5.15/1

|| 演算子グループは左から右に。オペランドは両方とも暗黙的にboolに変換されます(4節)。いずれかのオペランドがtrueの場合はtrueを返し、それ以外の場合はfalseを返します。|、||とは異なり 左から右への評価を保証します。さらに、最初のオペランドがtrueと評価された場合、2番目のオペランドは評価されません。

それはそれらの隣に両方のために言います:

結果はブール値です。一時的な破壊(12.2)を除いて、最初の式のすべての副作用は、2番目の式が評価される前に発生します。

それに加えて、1.9/18言います

それぞれの表現の評価において

  • a && b
  • a || b
  • a ? b : C
  • a , b

これらの式(5.14、5.15、5.16、5.18)で演算子の組み込みの意味を使用すると、最初の式の評価の後にシーケンスポイントがあります。


10

古き良きK&Rから直接:

Cはそのこと&&を保証し、||左から右に評価されます—これが問題になるケースをすぐに見ます。


3
K&R 2nd edition p40。「&&または||で接続された式は左から右に評価され、結果の真偽が判明するとすぐに評価が停止します。ほとんどのCプログラムはこれらのプロパティに依存しています。」引用されたテキストが本のどこにも見つかりません。これは非常に古い第1版からのものですか?このテキストを見つけた場所を明確にしてください。
ランディン2017

1
この古代のチュートリアルを引用していることがわかりました。それは1974年からであり、非常に無関係です。
ランディン2017

6

非常に注意してください。

基本タイプの場合、これらはショートカット演算子です。

ただし、これらの演算子を独自のクラスまたは列挙型に対して定義した場合、それらはショートカットではありません。これらのさまざまな状況下での使用法のこの意味上の違いのため、これらの演算子を定義しないことをお勧めします。

以下の場合operator &&operator ||基本的なタイプのための評価順序は、(そうでない場合は、短いカットは難しいだろう、左から右に:-)しかし、あなたが定義することをオーバーロード演算子のために、これらは基本的にメソッドを定義するシンタックスシュガーであり、したがって、パラメータの評価の順序はあるさ未定義。


1
オペレーターのオーバーロードは、タイプがPODであるかどうかとは関係ありません。演算子関数を定義するには、引数の少なくとも1つがクラス(または構造体または共用体)、列挙型、またはそれらの1つへの参照である必要があります。PODであることは、memcpyを使用できることを意味します。
Derek Ledbetter

そして、それは私が言っていたものです。クラスの&&をオーバーロードした場合、それは実際には単なるメソッド呼び出しです。したがって、パラメーターの評価順序に依存することはできません。明らかに、PODタイプの&&をオーバーロードすることはできません。
マーティンヨーク

3
「PODタイプ」という用語を誤って使用しています。構造体、クラス、共用体または列挙型、PODかどうかにかかわらず、&&をオーバーロードできます。両側が数値型またはポインタの場合、&&をオーバーロードすることはできません。
Derek Ledbetter

私はPODを(char / int / floatなど)集約POD(これはあなたが話していることではありません)として使用していましたが、通常は組み込み型ではないため、個別にまたはより明示的に参照されます。
マーティンヨーク

2
「基本型」という意味で「POD型」と書いたんですか?
OO Tiib

0

あなたの質問は、C ++演算子の優先順位と結合性に帰着します。基本的に、複数の演算子があり括弧がない式では、コンパイラーはこれらの規則に従って式ツリーを作成します。

優先順位として、のようなものがA op1 B op2 Cある場合は、(A op1 B) op2 Cまたはとしてグループ化できますA op1 (B op2 C)。のop1優先順位がよりも高い場合op2、最初の式が取得されます。それ以外の場合は、2番目を取得します。

関連性のために、あなたのような何かを持っているときA op B op C、あなたの可能性を再びグループとして薄くなり(A op B) op CかをA op (B op C)op結合性を残している場合は、最初の式になります。正しい関連付けがある場合は、2番目の関連付けになります。これは、同じ優先レベルの演算子でも機能します。

この特定のケースで&&は、はよりも優先順位が高い||ため、式はとして評価され(a != "" && it == seqMap.end()) || isEvenます。

順序自体は、式ツリー形式では「左から右」です。したがって、最初にを評価しa != "" && it == seqMap.end()ます。真の場合は式全体が真であり、そうでない場合はに進みisEvenます。手続きはもちろん、左部分式の中で再帰的に繰り返されます。


興味深い一口ですが、優先順位の概念は数学表記にそのルーツがあります。同じことがで発生します。a*b + cでは、の*優先順位が高くなってい+ます。

さらに興味深い/あいまいな、A1 op1 A2 op2 ... opn-1 Anすべての演算子の優先順位が同じである、括弧なしの式の場合、形成できるバイナリ式ツリーの数は、いわゆるカタロニア語の数によって与えられます。大規模なn場合、これらは非常に速く成長します。d


これはすべて正しいですが、それは演算子の優先順位と結合性に関するものであり、評価の順序と短絡に関するものではありません。それらは違うものです。
Thomas Padron-McCarthy

0

ウィキペディアを信頼している場合:

[ &&および||]は、ビット単位の演算子&および|と意味的に異なります。結果が左から判断できる場合、右のオペランドを評価することはないため

C(プログラミング言語)


11
標準があるのに、なぜwikiを信頼するのですか?
マーティンヨーク


C ++のオーバーロードされた演算子は短絡されないので、これはそれに関しては当てはまりますが、不完全です。
Thomas Padron-McCarthy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.