Cスタイル言語の論理NOT演算子が「~~」ではなく「!」なのはなぜですか?


39

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

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

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

NOTはANDおよびORとは反対の単項演算であると認識していますが、デザイナーがここでシングルがビット単位であり、ダブルが論理的であるという原則から逸脱し、代わりに異なるキャラクターを選んだ理由を考えることはできません。常にオペランド値を返すダブルビット演算のように、間違って読むことができると思います。しかし、それは私にとって本当の問題ではないようです。

行方不明の理由はありますか?


7
だって!! 論理的ではないことを意味し、42を1にするにはどうすればよいですか?:)
candied_orange

9
~~論理演算子がビット演算子の2倍になるというパターンに従うと、論理NOTの一貫性が向上しなかったでしょうか?
バートファンインゲンシェナウ

9
最初に、それが一貫性のためであれば、それは〜と~~だったでしょう。また、論理回路には短絡がありません。
クリストフ

3
典型的なユースケースでは、根本的な設計理由は視覚的な明快さと区別であると思われます。バイナリ(つまり、2オペランド)演算子は中置(およびスペースで区切られる傾向があります)に対して、単項演算子は接頭辞(および間隔が空けられません)です。
スティーブ

7
いくつかのコメントは、既に示唆した(とフォローしたくない人のためてきたように、このリンクを!!foo一般的ではない( -珍しいことではないではありません)イディオムそれは、ゼロ・オア・ゼロ以外の引数を正規化?。01
キース・トンプソン

回答:


108

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

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

これを読むと、Cがその言語設計の一部をその前身であるBCPL、特に演算子から継承していることが明らかになります。前述の資料の「新生児Cは、」どのようにBCPLの説明&|2つの新しい演算子で濃縮した&&||。その理由は次のとおりです。

  • と組み合わせて使用​​するため、異なる優先度が必要でした ==
  • 異なる評価ロジック:左から右への評価でショート (ときつまりはaあるfalsea&&bb評価されません)。

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

BCPLはすでに~ビットごとの否定に使用されています。したがって、一貫性の観点から、~~論理的意味を与えるためにa を与えるために2倍にした可能性があります。以来、残念ながら、これは非常に曖昧であったであろう~単項演算子である。~~bまた、意味するかもしれません~(~b))。これが、欠落している否定のために別のシンボルを選択する必要があった理由です。


10
パーサーは2つの状況を明確にすることはできないため、言語設計者はそうする必要があります。
BobDalgleish

16
@Steve:確かに、CおよびCライクな言語にはすでに多くの同様の問題があります。パーサは見ているとき(t)+1の追加ということである(t)1か、それがのキャストで+1型にt?C ++設計では、テンプレートを>>正しくlexする方法の問題を解決する必要がありました。等々。
エリックリッパー

6
@ user2357112私は、ポイントは、トークナイザが盲目的に取る持って大丈夫だということだと思う&&シングルとして&&2つのとしてトークンとない&ので、トークンa & (&b)解釈は書き込みに合理的なものではありませんので、人間は、とに驚いされていることを意味しなかっただろうとして扱うコンパイラa && b。両方の一方!(!a)!!a、それはコンパイラは、任意のトークン化レベルのルールとあいまいさを解決するための悪い考えですので、人間が意味するための可能なものです。
ベン

17
!!書くことが可能である/合理的であるだけでなく、標準的な「ブールに変換する」イディオム。
R ..

4
dan04は--avs の曖昧さを指していると思います-(-a)。どちらも構文的には有効ですが、セマンティクスが異なります。
ルスラン

49

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

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

&vs を考えるより良い方法&&は、バイナリブール値ではありません。より良い方法は、彼らを熱心zyだと考えることです。&オペレータは、左側と右側を実行し、結果を計算します。&&オペレータは、左側を実行し、その後、必要に応じて結果を計算するために右側のみを実行します。

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

それで一緒にしましょう。ブール値の配列で遅延操作を行うことは意味がありますか?いいえ、最初にチェックする「左側」がないためです。最初に確認する32の「左側」があります。我々は怠惰への操作制限するように、単一のブールを、その者のところそのうちの一つが、「バイナリ」で、もう1つは、「ブール」であるから来て、それがあることを、あなたの直感結果、設計のではなく、設計自体!

そして、そのように考えると、なぜno !!とno があるのか​​が明らかになり^^ます。これらの演算子のどちらにも、オペランドの1つを分析することをスキップできるプロパティがありません。「遅延」notまたは「なし」はありませんxor

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


2
ありがとうございました。これは私にとって本当の目を見張るものです。残念ながら、2つの答えを受け入れることはできません。
マーティン・マート

10
私は、これは考えるための良い方法だとは思わない&&&。熱意は間の違いの一つである一方&&&&完全に違ったの熱心なバージョンからの振る舞い&&、特に言語で、&&専用のboolean型以外のサポートタイプ。
user2357112はMonica

14
たとえば、CおよびC ++では、と1 & 2はまったく異なる結果になり1 && 2ます。
user2357112はMonica

7
@ZizyArcher:上記のコメントで述べたように、boolCで型を省略するという決定には、ノックオン効果があります。1つは「intを1つのブール値として扱う」という意味!~、もう1つは「intをブール値のパックされた配列として扱う」という意味なので、両方が必要です。bool型とint型が分かれている場合は、演算子を1つだけ持つことができます。これは私の考えではより優れた設計でしたが、私たちはその演算子にほぼ50年遅れています。C#は、親しみやすいようにこの設計を保持しています。
エリックリッパー

3
@Steve:答えがばかげているように思える場合、私はどこかで不十分に表現された議論をしており、当局からの議論に頼るべきではありません。それについて馬鹿げていると思われることについてもっと話してもらえますか?
エリックリッパー

21

TL; DR

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

ロングアンサー

歴史的に、Cは、BCPLに基づいた初期言語Bから発展しました。BCPLは、アルゴルに基づいたCPLに基づいていました。

C ++、Java、C#のgreat祖父であるAlgolは、プログラマーに直感的に感じられるようにtrueとfalseを定義しました。固有の積分値と同じ」。しかし、これの一つの欠点は、論理とビット単位ではないが、同じ操作することができないことである:任意の現代のコンピュータで~0-1よりもむしろ1に等しく、~1等しい-2なく0を(さらにいくつかの60歳のメインフレーム上に~0-を表します0またはINT_MIN~0 != 1これまでに作成されたすべてのCPUで、C言語標準は長年それを要求してきましたが、ほとんどの娘言語は符号と大きさまたは補数をまったくサポートすることさえしません)

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

BCPLには個別のブール型がありましnotビット単位および論理的ではない単一の演算子がありまし。このCの先駆者がその仕事をした方法は次のとおりでした。

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

ご了承ください true = ~ false

右辺値という用語は、Cファミリ言語ではまったく異なるものを意味するように進化していることがわかります。今日では、Cの「オブジェクト表現」と呼びます。)

この定義により、論理的およびビット単位で同じ機械語命令を使用できなくなります。Cがそのルートを行っていた場合、世界中のヘッダーファイルが言うでしょう#define TRUE -1

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

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

しかし、定義TRUEとして1及びFALSEとして0Bにはアイデンティティがあることを意味しないtrue = ~ false、もはや開催され、そしてそれはビット単位と論理式の間で明確にするためにアルゴルを許さ強い型付けを落としていました。これには新しい論理否定演算子が必要でしたが、設計者はを選択しました。これは!、おそらく不等号がすでに!=であったためです。これは、等号の縦棒のようなものです。彼らは、どちらもまだ存在していないため、&&または||どちらも存在しないため、同じ慣習に従っていませんでした。

おそらく、&B の演算子は設計どおりに壊れています。BおよびCにおいて、1 & 2 == FALSEたとえ12の両方truthy値であり、Cは、一部追加することによって修正しようとした1つのミスであったB.で論理演算表現する全く直感的な方法がない&&||、時間の主な関心事は、であったが最後に短絡を機能させ、プログラムをより速く実行します。これの証拠は、オペランドが両方とも真実であるにもかかわらず、^^1 ^ 2が真実の値ではないことですが、短絡の恩恵を受けることはできません。


4
+1。これは、これらのオペレーターの進化に関する非常に優れたガイド付きツアーだと思います。
スティーブ

ところで、入力が既にブール化されている場合でも、符号、大きさ、および補数のマシンは、ビットごとと論理否定を別々に必要とします。 ~0(すべてのビットセット)は、1の補数の負のゼロ(またはトラップ表現)です。ログイン/大きさは、~0最大の大きさと負の数です。
ピーターコーデス

@PeterCordesあなたは絶対に正しい。私は2の補数のマシンに焦点を合わせていました。なぜなら、それらははるかに重要だからです。たぶん、脚注に値するでしょう。
デイビスラー

私のコメントは十分だと思いますが、たぶん、括弧の付いたもの(1の補数や符号/大きさのいずれでも機能しない)が良い編集になるでしょう。
ピーターコーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.