web-dev-qa-db-ja.com

Cスタイル言語の論理NOT演算子が「!」である理由「~~」じゃない?

二項演算子には、ビット演算子と論理演算子の両方があります。

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

NOT(単項演算子)の動作は異なります。ビットごとに〜があります!論理的です。

ANDおよびORとは対照的に、NOTは単項演算であることを認識していますが、デザイナーがシングルがビット単位であり、ダブルがここでは論理的であるという原則から逸脱することを選択した理由は考えられません。代わりに別の文字を使用します。オペランド値を常に返すダブルビット演算のように、それを誤って読み取る可能性があります。

私が見逃している理由はありますか?

42
Martin Maat

奇妙なことに、Cスタイルのプログラミング言語の歴史はCで始まっていません。

デニス・リッチーはCの誕生の課題を この記事 で詳しく説明しています。

それを読むと、Cがその前身である [〜#〜] bcpl [〜#〜] 、特に演算子からその言語設計の一部を継承していることが明らかになります。前述の記事の「新生児C」セクションでは、BCPLの&|に、2つの新しい演算子&&||がどのように強化されているかを説明しています。その理由は:

  • ==と組み合わせて使用​​するため、異なる優先度が必要でした
  • 異なる評価ロジック: short-circuit を使用した左から右への評価(つまり、aa&&bfalseの場合、bは評価されません) 。

興味深いことに、この2倍にしても読者に曖昧さが生じることはありません。a && ba(&(&b))と誤って解釈されません。解析の観点からも、あいまいさはありません。bが左辺値の場合、&bは意味がありますが、ビット単位の&は整数のオペランドを必要とするので、これはポインターになるため、論理積は唯一の合理的な選択。

BCPLは、ビット単位の否定に~をすでに使用しています。したがって、一貫性の観点から見ると、~~を2倍にして論理的な意味を持たせることができます。残念ながら、~は単項演算子であるため、これは非常にあいまいでした。~~bは、~(~b))も意味する場合があります。これが、欠落した否定のために別の記号を選択しなければならない理由です。

110
Christophe

ここで、デザイナーがシングルがビット単位であり、ダブルが論理的であるという原則から逸脱することを選択した理由は考えられません。

そもそもそれは原則ではありません。それを理解したら、それはもっと理にかなっています。

&&&を比較するより良い方法は、binaryおよびBooleanではありません。より良い方法は、それらをeagerおよびlazyと考えることです。 &演算子は、左側と右側を実行してから、結果を計算します。 &&演算子は左側を実行し、結果を計算する必要がある場合にのみ右側を実行します。

さらに、「バイナリ」と「ブール」について考えるのではなく、実際に何が起こっているのかを考えてください。 「バイナリ」バージョンは、Wordにパックされたブール値の配列に対してブール演算を実行するだけです

まとめましょう。ブール演算の配列に対して遅延演算を実行することには意味がありますか?いいえ、最初にチェックする「左側」がないためです。最初にチェックする32の「左側」があります。したがって、遅延操作をsingleブール値に制限します。その1つが「バイナリ」で1つが「ブール値」であるという直感は、です。結果デザイン自体ではなく、デザインの!

そのように考えると、!!^^がない理由が明らかになります。これらの演算子には、いずれかのオペランドの分析をスキップできるプロパティはありません。 「怠惰な」notxorはありません。

他の言語はこれをより明確にします。たとえば、一部の言語ではandを使用して「熱心なand」を意味しますが、and alsoは「怠惰なand」を意味します。また、他の言語では、&&&が「バイナリ」でも「ブール」でもないことが明確になっています。たとえばC#では、両方のバージョンでブール値をオペランドとして使用できます。

51
Eric Lippert

TL; DR

Cは別の言語から!および~演算子を継承しました。 &&||の両方が数年後に別の人によって追加されました。

長い答え

歴史的に、Cは、ALGOLに基づくCPLに基づくBCPLに基づく初期言語Bから開発されました。

[〜#〜] algol [〜#〜] 、C++の偉大な祖父、JavaおよびC#、感じるようになった方法でtrueおよびfalseを定義プログラマーにとって直感的:「2進数(1はtrue、0はfalse)と見なされる真理値は、固有の整数値と同じです。」しかし、これの1つの欠点は、論理的およびビットごとに同じ操作:最近のコンピューターでは、~0は1ではなく-1に等しく、~1は0ではなく-2に等しくなります(~0が表す60年前のメインフレームでも-これまでに作成されたすべてのCPUで0またはINT_MIN~0 != 1であり、C言語標準では何年もの間それを要求してきましたが、そのドーター言語のほとんどは、サインアンドマグニチュードや完全に補数です。)

ALGOLは、ブールモードと積分モードで異なるモードを使用し、演算子を異なる方法で解釈することにより、これを回避しました。つまり、ビット演算は整数型に対するものであり、論理演算はブール型に対するものでした。

BCPLには個別のブール型がありましたが、ビット単位および論理否定の両方で 単一のnot演算子 です。このCの初期の先駆者がその作業を行った方法は次のとおりです。

真のR値は、完全に1で構成されるビットパターンです。 falseのR値はゼロです。

true = ~ false

rvalueという用語は、Cファミリ言語ではまったく異なるものを意味するように進化したことに気づくでしょう。今日では、これを「オブジェクト表現」と呼びます。 C)

この定義により、論理的およびビット単位で同じ機械語命令を使用できなくなります。 Cがそのルートを通過した場合、世界中のヘッダーファイルには#define TRUE -1と表示されます。

しかし Bプログラミング言語 は弱く型付けされており、ブール型も浮動小数点型もありませんでした。後継のCでは、すべてがintと同等でした。これにより、プログラムが論理値としてtrueまたはfalse以外の値を使用した場合に何が起こるかを定義することは、言語にとって良いアイデアとなりました。それは最初に真実の表現を「ゼロに等しくない」と定義しました。これは、CPUゼロフラグが設定されたミニコンピューターで効率的でした。

当時は代替案がありました。同じCPUにも負のフラグがあり、BCPLの真理値は-1でした。そのため、Bはすべての負の数を真と定義し、すべての負でない数を偽と定義した可能性があります。 (このアプローチには1つの残党があります。同じ人が同時に開発したUNIXは、すべてのエラーコードを負の整数として定義します。そのシステムコールの多くは、失敗するといくつかの異なる負の値の1つを返します。)感謝します:悪化している可能性があります!

しかし、TRUE1として定義し、FALSE0としてBで定義すると、ID true = ~ falseが保持されなくなり、強い型付けができなくなりました。 ALGOLがビット単位の式と論理式のあいまいさを解消できるようにしました。これには新しいlogical-not演算子が必要で、デザイナーは!を選択しました。これは、等しくないtoが既に!=であったためであり、等号の縦棒のようなものに見えます。どちらもまだ存在しないため、&&または||と同じ規則に従っていませんでした。

間違いなく、それらは必要です:Bの&演算子は設計どおりに壊れています。 BとCでは、1 & 2 == FALSE1はどちらも真の値であるにもかかわらず、2であり、Bで論理演算を表現する直観的な方法はありません。 &&||を追加して部分的に修正しますが、当時の主な関心事は、最終的に短絡回路を機能させ、プログラムをより高速に実行することでした。これの証明は、^^がないことです。1 ^ 2は、両方のオペランドが真であるにもかかわらず、真の値ですが、短絡から利益を得ることができません。

22
Davislor