私の知る限り、C は論理式に遅延計算を使用します。
f(x) && g(x)
g(x)
f(x)
false の場合は呼び出されません。
しかし、次のような算術式はどうですか
f(x)*g(x)
がゼロのg(x)
場合に呼び出されますか?f(x)
私の知る限り、C は論理式に遅延計算を使用します。
f(x) && g(x)
g(x)
f(x)
false の場合は呼び出されません。
しかし、次のような算術式はどうですか
f(x)*g(x)
がゼロのg(x)
場合に呼び出されますか?f(x)
はい、算術演算は積極的であり、怠惰ではありません。
そのため、f(x)*g(x)
両方 f
で とが常に呼び出されます (衒学的なことに、コンパイラはそれを何らかのA-normal 形式g
に変換しており、それが観察できない場合は一部の呼び出しを回避することさえできます) が、 before または afterを呼び出す順序について保証はありません。が 0 の場合、または評価は未定義の動作です。f
g
x*1/x
y*1/x
x
これはHaskell AFAIUには当てはまりません
はい、g(x)
引き続き呼び出されます。
一般に、左辺が 0 であるという理由だけで右辺の評価を条件付きで除外するのは非常に遅くなります。おそらく、右側が高価な関数呼び出しである場合はそうではありませんが、コンパイラーはそれを認識していません。
怠け者ではなく「短絡」と呼ばれます。そして、少なくとも標準が気にかけている限り、そうです。つまり、 の短絡評価を指定していません*
。
g()
コンパイラは、副作用がないことが確実である場合、短絡評価を実行できる可能性がありますが、as-if ルールの下でのみ実行できます (つまり、標準の違いによるものではなく、外部から観察可能な違いがないことを確認することによってのみ実行できます)。そうするための直接の許可を与えます)。
その最適化は可能ですが、それに対していくつかの議論があります。
最適化から得られるよりも多くを支払う可能性があります。論理演算子とは異なり、最適化は、算術演算子を使用したすべてのケースのごく一部でのみ有益である可能性がありますが、同時に、0 の追加チェックが必要です。すべての操作。
ブール値の真理値には可能な値が 2 つしかないため、2 番目のオペランドを評価する必要がない短絡ブール式を使用すると、理論的に 50% の可能性 (1 ÷ 2) があります。(これは一様分布を前提としていますが、これはおそらく現実的ではありませんが、ご容赦ください。) つまり、比較的大きな割合のケースで最適化から利益が得られる可能性があります。
これを整数と対比してください。0 は数百万の可能な値のうちの 1 つにすぎません。最初のオペランドが 0 である確率ははるかに低く、1 ÷ 2 32 (32 ビット整数の場合、ここでも一様分布を仮定) です。実際に 0 がそれよりも発生する可能性がいくらか高かったとしても (つまり、不均一な分布で)、真理値と同じ大きさのオーダーを扱っている可能性は低いです。
浮動小数点演算は、この問題をさらに悪化させます。ここでは、丸め誤差と非正規化の可能性に対処する必要があります。一部の計算で正確に0 が得られる確率は、整数の場合よりもさらに低くなる可能性があります。
したがって、最適化によって残りのオペランドが評価されないという結果になる可能性は比較的低くなります。しかし、ゼロ、100% の確率で追加のチェックが行われます!
評価規則の一貫性を適度に保ちたい場合は、 と の短絡評価順序を再定義する必要が&&
あり||
ます:除算には 1 つの重要なコーナー ケース、つまり 0 による除算があります: 最初のオペランドが 0 であっても、商は必ずしも 0 ではありません。 0 による除算はエラーとして扱われます (おそらく IEEE 浮動小数点演算を除く)。したがって、計算が有効かどうかを判断するには、常に 2 番目のオペランドを評価する必要があります。
には 1 つの代替最適化があり/
ます: 1 による除算です。その場合、除算はまったく必要なく、単純に最初のオペランドを返します。/
したがって、2 番目のオペランド (除数) から開始することによって、より最適化されます。
ここで、 、 、 を最初のオペランドで評価を開始したいが、2 番目のオペランドで開始したくない場合&&
(||
これ*
は直観的ではないように思われるかもしれません) を除き、通常は、2 番目のオペランドが常に最初に評価される/
ように短絡動作を再定義する必要があります。、それは現状からの逸脱になります。
これ自体は問題ではありませんが、C 言語がこのように変更された場合、多くの既存のコードが壊れる可能性があります。
最適化により、演算子をオーバーロードできるC++ コードとの「互換性」が失われる可能性があります。*
最適化はオーバーロードされたand/
演算子にも適用されますか? それとも、これらの演算子には 2 つの異なる形式 (1 つはショートサーキット、もう 1 つは熱心な評価) が必要でしょうか?
繰り返しますが、これは短絡算術演算子に固有の欠陥ではありませんが、そのような短絡が重大な変更として C (および C++) 言語に導入された場合に発生する問題です。
論理演算子の場合、評価&&
の||
順序は左から右にバインドされ、短絡が発生します。&&
(論理 AND)、||
(論理 OR) (短絡評価の一部として)の左右のオペランドの評価の間には、シーケンス ポイントがあります。たとえば、式*p++ != 0 && *q++ != 0
では、部分式のすべての副作用が*p++ != 0
にアクセスしようとする前に完了しますq
が、算術演算子 の場合はそうではありません。