web-dev-qa-db-fra.com

Pourquoi l'opérateur NOT logique dans les langages de style C "!" et non "~~"?

Pour les opérateurs binaires, nous avons à la fois des opérateurs binaires et logiques:

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

NOT (un opérateur unaire) se comporte cependant différemment. Il y a ~ pour bit à bit et! pour logique.

Je reconnais que NOT n'est pas une opération unaire par opposition à AND et OR mais je ne peux pas penser à une raison pour laquelle les concepteurs ont choisi de s'écarter du principe selon lequel le simple est au niveau du bit et le double est logique ici, et est allé pour un caractère différent à la place. Je suppose que vous pourriez le mal lire, comme une double opération au niveau du bit qui retournerait toujours la valeur de l'opérande. Mais cela ne me semble pas vraiment un problème.

Y a-t-il une raison pour laquelle je manque?

42
Martin Maat

Curieusement, l'histoire du langage de programmation de style C ne commence pas par C.

Dennis Ritchie explique bien les défis de la naissance de C dans cet article .

En le lisant, il devient évident que C a hérité une partie de la conception de son langage de son prédécesseur BCPL , et surtout des opérateurs. La section "Néonatal C" de l'article susmentionné explique comment & Et | De BCPL ont été enrichis avec deux nouveaux opérateurs && Et ||. Les raisons étaient les suivantes:

  • une priorité différente était requise en raison de son utilisation en combinaison avec ==
  • logique d'évaluation différente: évaluation de gauche à droite avec court-circuit (c'est-à-dire lorsque a est false dans a&&b, b n'est pas évalué).

Fait intéressant, ce doublement ne crée aucune ambiguïté pour le lecteur: a && b Ne sera pas mal interprété comme a(&(&b)). D'un point de vue d'analyse, il n'y a pas d'ambiguïté non plus: &b Pourrait avoir un sens si b était une valeur l, mais ce serait un pointeur alors que le bit & Exigerait un opérande entier, donc le ET logique serait le seul choix raisonnable.

BCPL a déjà utilisé ~ Pour la négation au niveau du bit. Donc, d'un point de vue de la cohérence, il aurait pu être doublé pour donner un ~~ Pour lui donner sa signification logique. Malheureusement, cela aurait été extrêmement ambigu puisque ~ Est un opérateur unaire: ~~b Pourrait également signifier ~(~b)). C'est pourquoi un autre symbole a dû être choisi pour la négation manquante.

110
Christophe

Je ne peux pas penser à une raison pour laquelle les concepteurs ont choisi de s'écarter du principe selon lequel le simple est au niveau du bit et le double est logique ici,

Ce n'est pas le principe en premier lieu; une fois que vous vous en rendez compte, cela a plus de sens.

La meilleure façon de penser à & Vs && N'est pas binaire et booléen. La meilleure façon est de les considérer comme impatient et paresseux. L'opérateur & Exécute les côtés gauche et droit, puis calcule le résultat. L'opérateur && Exécute le côté gauche, puis exécute le côté droit uniquement si nécessaire pour calculer le résultat.

De plus, au lieu de penser à "binaire" et "booléen", pensez à ce qui se passe réellement. La version "binaire" est juste faisant l'opération booléenne sur un tableau de booléens qui a été compressé dans un mot.

Alors mettons-le ensemble. Est-il logique de faire une opération paresseuse sur un tableau de booléens? Non, car il n'y a pas de "côté gauche" à vérifier en premier. Il y a 32 "côtés gauche" à vérifier en premier. Nous limitons donc les opérations paresseuses à un nique booléen, et c'est de là que vient votre intuition que l'un d'eux est "binaire" et l'autre "booléen", mais c'est un conséquence du design, pas du design lui-même!

Et quand on y pense de cette façon, il devient clair pourquoi il n'y a ni !! Ni ^^. Aucun de ces opérateurs n'a la propriété que vous pouvez ignorer en analysant l'un des opérandes; il n'y a pas de "paresseux" not ou xor.

D'autres langues rendent cela plus clair; certaines langues utilisent and pour signifier "impatient et" mais and also pour signifier "paresseux et", par exemple. Et d'autres langues indiquent également plus clairement que & Et && Ne sont pas "binaires" et "booléens"; en C # par exemple, les deux versions peuvent prendre des booléens comme opérandes.

51
Eric Lippert

TL; DR

C a hérité du ! et ~ opérateurs d'une autre langue. Tous les deux && et || ont été ajoutés des années plus tard par une autre personne.

Longue réponse

Historiquement, C s'est développé à partir des premiers langages B, qui était basé sur BCPL, qui était basé sur CPL, qui était basé sur ALGOL.

ALGOL , l'arrière-grand-papa de C++, Java et C #, défini vrai et faux d'une manière qui se faisait sentir intuitif pour les programmeurs: "les valeurs de vérité qui, considérées comme un nombre binaire (vrai correspondant à 1 et faux à 0), sont les mêmes que la valeur intégrale intrinsèque". Cependant, un inconvénient de cela est que logique et bit à bit ne peut pas être même opération: sur tout ordinateur moderne, ~0 vaut -1 au lieu de 1 et ~1 est égal à -2 plutôt qu'à 0. (Même sur un ordinateur central âgé de soixante ans où ~0 représente -0 ou INT_MIN, ~0 != 1 sur chaque CPU jamais fabriqué, et la norme du langage C l'exige depuis de nombreuses années, tandis que la plupart de ses langages filles ne prennent même pas la peine de prendre en charge la signature et l'amplitude ou le complément à un.)

ALGOL a travaillé autour de cela en ayant différents modes et en interprétant les opérateurs différemment en mode booléen et intégral. Autrement dit, une opération au niveau du bit était une sur les types entiers et une opération logique en était une sur les types booléens.

BCPL avait un type booléen séparé, mais n seul opérateur not , pour les deux bits et non logique. La façon dont ce précurseur précoce de C a fait ce travail était la suivante:

La valeur R de true est un motif binaire entièrement composé de uns; la valeur R de false est nulle.

Notez que true = ~ false

(Vous remarquerez que le terme rvalue a évolué pour signifier quelque chose de complètement différent dans les langages de la famille C. Nous appellerions aujourd'hui cela "la représentation d'objet" en C.)

Cette définition permettrait à logique et au niveau du bit de ne pas utiliser la même instruction en langage machine. Si C avait suivi cette voie, les fichiers d'en-tête du monde entier diraient #define TRUE -1.

Mais le langage de programmation B était faiblement typé, et n'avait aucun type booléen ou même à virgule flottante. Tout était l'équivalent de int dans son successeur, C. Cela en faisait une bonne idée pour le langage de définir ce qui se passait lorsqu'un programme utilisait une valeur autre que true ou false comme valeur logique. Il a d'abord défini une expression véridique comme "non égale à zéro". Cela était efficace sur les mini-ordinateurs sur lesquels il fonctionnait, qui avaient un indicateur CPU zéro.

Il y avait, à l'époque, une alternative: les mêmes processeurs avaient également un indicateur négatif, et la valeur de vérité de BCPL était -1, donc B aurait peut-être plutôt défini tous les nombres négatifs comme véridiques et tous les nombres non négatifs comme fausses. (Il y a un vestige de cette approche: UNIX, développé par les mêmes personnes en même temps, définit tous les codes d'erreur comme des nombres négatifs. Beaucoup de ses appels système renvoient l'une des différentes valeurs négatives en cas d'échec.) Soyez donc reconnaissant: il Aurait pu être pire!

Mais en définissant TRUE comme 1 et FALSE comme 0 en B signifiait que l'identité true = ~ false ne tenait plus, et il avait abandonné le typage fort qui permettait à ALGOL de lever l'ambiguïté entre les expressions binaires et logiques. Cela nécessitait un nouvel opérateur logique et non, et les concepteurs ont choisi !, peut-être parce que différent de était déjà !=, qui ressemble à une barre verticale à travers un signe égal. Ils n'ont pas suivi la même convention que && ou || car aucun n'existait encore.

Sans doute, ils devraient avoir: le & L'opérateur en B est cassé comme prévu. En B et en C, 1 & 2 == FALSE même si 1 et 2 sont deux valeurs véridiques, et il n'y a aucun moyen intuitif d'exprimer l'opération logique en B. C'était une erreur que C a tenté de rectifier en partie en ajoutant && et ||, mais la principale préoccupation à l'époque était de faire enfin fonctionner le court-circuit et d'accélérer l'exécution des programmes. La preuve en est qu'il n'y a pas de ^^: 1 ^ 2 est une valeur véridique même si ses deux opérandes sont véridiques, mais il ne peut pas bénéficier d'un court-circuit.

22
Davislor