En C++, pourquoi true & true | / false & false = = true?
j'aimerais savoir si quelqu'un connaît la manière d'un compilateur pourrait interpréter le code suivant:
#include <iostream>
using namespace std;
int main() {
cout << (true && true || false && false) << endl; // true
}
Est-ce vrai parce que && a une priorité plus élevée que || ou parce que || est un opérateur de court-circuit (en d'autres termes, est-ce qu'un opérateur de court-circuit ignore toutes les expressions subséquentes, ou juste la prochaine expression)?
12 réponses
Caladain a exactement la bonne réponse, mais je voulais répondre à un de vos commentaires sur sa réponse:
en cas de court-circuit de l'opérateur || et de court-circuit de l'exécution de la seconde & expression, cela signifie que l'opérateur | a été exécuté avant le second opérateur&&. Cela implique une exécution de gauche à droite pour & & & et || (pas & & de priorité).
je pense qu'une partie du problème est que la préséance ne pas tout dire ce que vous pensez que cela signifie. Il est vrai que &&
a priorité plus élevée que ||
, et cela explique exactement le comportement que vous voyez. Prenons le cas des opérateurs arithmétiques ordinaires: supposons que nous ayons a * b + c * (d + e)
. Ce qui nous indique la préséance, c'est Comment insérer des parenthèses: d'abord autour de *
autour de +
. Cela nous donne (a * b) + (c * (d + e))
; dans votre cas, nous avons (1 && 1) || (infiniteLoop() && infiniteLoop())
. Alors, imaginez que les expressions deviennent arbres. Pour ce faire, transformez chaque opérateur dans un noeud avec ses deux arguments comme enfants:
l'Évaluation de cet arbre court-circuit. Dans l'arbre arithmétique, vous pouvez imaginer un style d'exécution large-first bottom-up: first evaluate DE = d + e
, puis AB = a * b
et CDE = c * DE
, et le résultat final est AB + CDE
. Mais notez que vous pouvez également ont bien évalué AB
d'abord, puis DE
,CDE
, et le résultat final; vous ne pouvez pas dire différence. Cependant, depuis ||
et &&
sont à court-circuiter, ils pour utiliser cette évaluation la plus à gauche. Ainsi, pour évaluer l' ||
, nous évaluons d'abord 1 && 1
. Comme c'est vrai, ||
court-circuite et ignore sa branche droite-même si,si il l'avait évalué, il aurait dû évaluer le infiniteLoop() && infiniteLoop()
en premier.
Si cela peut aider, vous pouvez penser à chaque nœud de l'arbre comme un appel de fonction, qui produit la représentation suivante plus(times(a,b), times(c,plus(d,e)))
dans le premier cas, et or(and(1,1), and(infiniteLoop(),infiniteLoop())
dans le deuxième cas. Court-circuiter signifie que vous devez évaluer pleinement chaque argument de la fonction de gauche à or
ou and
;true
(pour or
) ou false
(pour and
), alors ignorez l'argument de droite.
votre commentaire présuppose que nous évaluons tout d'abord tout ce qui a la plus haute priorité, puis tout ce qui a la plus haute priorité, et ainsi de suite, ce qui correspond à une première exécution de l'arbre par le bas. Au lieu de cela, ce qui se passe est que la préséance nous dit comment construire l'arbre. Les règles d'exécution de l'arbre ne sont pas pertinentes dans le cas simple de l'arithmétique; court-circuiter, cependant, est précisément une spécification exacte de la façon d'évaluer l'arbre.
Edit 1: Dans l'un de vos autres commentaires, vous avez dit
votre exemple arithmétique nécessite les deux multiplications à évaluer avant l'addition finale, n'est-ce pas ce qui définit la préséance?
Oui, c'est ce qui définit la préséance-sauf que ce n'est pas tout à fait vrai. C'est certainement exactement vrai dans C, mais considérez comment vous évalueriez le (non-C!) l'expression 0 * 5 ^ 7
dans votre tête, où 5 ^ 7 = 57
et ^
a priorité plus élevée que *
. Selon votre règle de base, nous devons évaluer 0
et 5 ^ 7
avant de nous peut trouver le résultat. Mais vous ne prendriez pas la peine d'évaluer 5 ^ 7
; vous serait juste de dire "eh bien, puisque 0 * x = 0
pour tout x
, ce doit être 0
", et sauter toute la branche droite. En d'autres termes, je n'ai pas évalué les deux côtés complètement avant d'évaluer la multiplication finale; j'ai court-circuité. De même, depuis false && _ == false
et true || _ == true
pour tout _
, nous ne pouvons pas besoin de toucher le côté droit; c'est ce que signifie pour un opérateur de court-circuit. C n'est pas la multiplication en court-circuit (bien qu'un langage puisse le faire), mais elle court-circuit &&
et ||
.
tout aussi court-circuitant 0 * 5 ^ 7
ne change pas les règles de préséance habituelles des PEMDAS, court-circuiter les opérateurs logiques ne change pas le fait que &&
a priorité plus élevée que ||
. C'est tout simplement un raccourci. Puisque nous devons choisir côté de l'opérateur pour évaluer d'abord, c promet d'évaluer côté gauche des opérateurs logiques d'abord; une fois fait, il y a un moyen évident (et utile) d'éviter d'évaluer le côté droit pour certaines valeurs, et C promet de le faire.
votre règle-évaluer l'étendue de l'expression-d'abord de bas en haut-est également bien définie, et une langue pourrait choisir de le faire. Cependant, il a l'inconvénient de ne pas permettre le court-circuitage, ce qui est un comportement utile. Mais si chaque expression dans votre arbre est bien défini (pas de boucles) et pure (pas de variables modificatrices, impression, etc.), alors vous ne pouvez pas faire la différence. C'est seulement dans ces cas étranges, que les définitions mathématiques de "et" et "ou" ne couvrent pas, que le court-circuitage est même visible.
notez aussi qu'il n'y a rien fondamentaux sur le fait que court-circuiter fonctionne en priorisant l'expression la plus à gauche. On pourrait définir un langage de balisage, où ⅋⅋
représente and
et \
représente ||
, mais où 0 ⅋⅋ infiniteLoop()
et 1 \ infiniteLoop()
boucle infiniteLoop() ⅋⅋ 0
et infiniteLoop() \ 1
serait faux et vrai, respectivement. Cela correspond simplement à choisir d'évaluer le côté droit d'abord au lieu du côté gauche, et ensuite simplifier de la même manière.
En bref: ce que la préséance nous dit c'est comment construire le parse. Les seuls ordres sensés pour évaluer l'arbre parse sont ceux qui se comportent si nous évaluons son étendue-d'abord bottom-up (comme vous voulez) sur des valeurs pures bien définies. Pour non-défini ou impur valeurs, ordre linéaire doit être choisi. 1 une fois qu'un ordre linéaire est choisi, certaines valeurs d'un côté d'un opérateur peuvent déterminer de façon unique le résultat de l'expression entière ( e.g.,0 * _ == _ * 0 == 0
,false && _ == _ && false == false
, ou true || _ == _ || true == true
). Pour cette raison, vous pouvez être en mesure de s'échapper sans terminer l'évaluation de ce qui vient par la suite dans le ordre linéaire; C promet de le faire pour les opérateurs logiques &&
et ||
en les évaluant de gauche à droite, et de ne pas le faire pour quoi que ce soit d'autre. Cependant, grâce à la préséance, nous true || true && false
true
et non false
: car
true || true && false
→ true || (true && false)
→ true || false
→ true
au lieu de
true || true && false
↛ (true || true) && false
→ true && false
→ false
1: en Fait, on pourrait théoriquement évaluer les deux côtés d'un opérateur en parallèle, mais ce n'est pas cela donne lieu à une sémantique plus souple, mais qui a des problèmes avec les effets secondaires (quand se produisent-ils?).
(true & true || false& false) est évalué avec && ayant une priorité plus élevée.
TRUE && TRUE = True
FALSE && FALSE = False
True || False = True
mise à Jour:
1&&1||infiniteLoop()&&infiniteLoop()
pourquoi cela produit-il true en C++?
comme avant, séparons-le. && a une priorité plus élevée que / / et les déclarations booléennes court-circuitent en C++.
1 && 1 = True.
quand une valeur bool est convertie en une valeur entière, alors
false -> 0
true -> 1
l'expression évalue ceci (vrai) && (vrai) déclaration, qui court-circuite le ||, ce qui empêche les boucles infinies de l'exécution. Il y a beaucoup plus de compilateur Juju en cours, donc c'est une vue simpliste de la situation qui est adéquate pour cet exemple.
dans un environnement non court-circuité, cette expression serait suspendue à jamais parce que les deux côtés du bloc opératoire seraient "évalués" et le côté droit serait suspendu.
si vous êtes confus au sujet de la priorité, voici comment les choses évalueraient dans votre original post si || a une priorité plus élevée que &&:
1st.) True || False = True
2nd.) True && 1st = True
3rd.) 2nd && false = false
Expression = False;
je ne me souviens pas si elle va de droite à gauche ou de gauche à droite, mais le résultat serait le même. Dans ton deuxième post, si || les plus élevés avaient precendence:
1st.) 1||InfLoop(); Hang forever, but assuming it didn't
2nd.) 1 && 1st;
3rd.) 2nd && InfLoop(); Hang Forever
tl;dr: C'est toujours la préséance qui fait que le &&'s soit évalué en premier, mais le compilateur court-circuite aussi le bloc opératoire. Essentiellement, le compilateur regroupe l'ordre des opérations comme ceci( vue simpliste, mettre bas les fourches :-P)
1st.) Is 1&&1 True?
2nd.) Evaluate if the Left side of the operation is true,
if so, skip the second test and return True,
Otherwise return the value of the second test(this is the OR)
3rd.) Is Inf() && Inf() True? (this would hang forever since
you have an infinite loop)
mise à jour #2: "Cependant, cet exemple prouve & &N'a pas priorité, car le || est évalué avant le second&&. Cela montre que && et || ont la même priorité et sont évalués de gauche à droite."
"Si && a préséance il permettrait d'évaluer le premier && (1), puis la seconde && (boucle infinie) et accrocher le programme. Puisque cela ne se produit pas, && n'est pas évalué avant | / ."
examinons en détail.
nous parlons ici de deux choses distinctes. Priorité, qui détermine l'Ordre des opérations, et court-circuitage, qui est un truc de compilateur/langage pour sauver les cycles de processeur.
couvrons D'abord la priorité. La préséance est un raccourci pour" ordre des opérations " essentiellement, compte tenu de cette déclaration: 1 + 2 * 3 dans quel ordre les opérations seront regroupés pour l'évaluation?
les mathématiques définissent clairement l'ordre des opérations en donnant à la multiplication une priorité supérieure à l'addition.
1 + (2 * 3) = 1 + 2 * 3
2 * 3 is evaluated first, and then 1 is added to the result.
* has higher precedence than +, thus that operation is evaluated first.
maintenant, passons aux expressions booléennes: (&&=AND, / / = OR)
true AND false OR true
C++ ET donne une priorité supérieure OU inférieure, donc
(true AND false) OR true
true AND false is evaluated first, and then
used as the left hand for the OR statement
Donc, juste sur la préséance, (true && true || false && false) va être opéré dans cet ordre:
((true && true) || (false && false)) = (true && true || false && false)
1st Comparison.) true && true
2nd Comparison.) false && false
3rd Comparison.) Result of 1st comparison || Result of Second
avec moi jusqu'à présent? Maintenant permet d'obtenir en Court-Circuit: En C++, Les déclarations booléennes sont ce qu'on appelle "court-circuité". Cela signifie que le compilateur va regarder un énoncé donné un choix du "meilleur chemin" pour l'évaluation. Prenez cet exemple:
(true && true) || (false && false)
There is no need to evaluate the (false && false) if (true && true)
equals true, since only one side of the OR statement needs to be true.
Thus, the compiler will Short Circuit the expression. Here's the compiler's
Simplified logic:
1st.) Is (true && true) True?
2nd.) Evaluate if the Left side of the operation is true,
if so, skip the second test and return True,
Otherwise return the value of the second test(this is the OR)
3rd.) Is (false && false) True? Return this value
comme vous pouvez le voir, si (true & true) est évalué TRUE, alors il n'est pas nécessaire de passer les cycles d'horloge à évaluer si (false& false) est true.
C++ toujours Court-Circuts, mais d'autres langues fournissent des mécanismes pour ce qu'on appelle "Eager"" opérateur.
Prenez par exemple le langage de programmation Ada. Dans L'Ada," et "et" ou "sont des opérateurs" impatients"..ils forcent tout à être évalué.
En Ada (vrai ET vrai) OU (faux ET faux) permettrait d'évaluer à la fois (vrai ET vrai) et (faux ET faux) avant d'évaluer le OU. Ada vous donne aussi la possibilité de court-circuiter avec et puis et ou bien, ce qui vous donnera le même comportement que C++.
j'espère que cela répondra pleinement à votre question. Si non, laissez-moi savoir :-)
mise à jour 3: dernière mise à jour, et puis je continuerai sur email si vous avez encore des problèmes.
" en cas de court-circuit de l'opérateur || et de court-circuit de l'exécution de la seconde & expression, cela signifie que l'opérateur | a été exécuté avant le second opérateur&&. Cela implique une exécution de gauche à droite pour & & & et || (pas & & de priorité)."
regardons alors ce exemple:
(false && infLoop()) || (true && true) = true (Put a breakpoint in InfLoop and it won't get hit)
false && infLoop() || true && true = true (Put a breakpoint in InfLoop and it won't get hit)
false || (false && true && infLoop()) || true = false (infLoop doesn't get hit)
si ce que vous disiez était vrai, InfLoop serait touché dans les deux premiers. Vous remarquerez aussi Qu'InfLoop () n'est pas appelé dans le troisième exemple non plus.
maintenant, regardons ceci:
(false || true && infLoop() || true);
L'Infloop est appelé! Si, ou si, il y avait une plus grande précendance que&&, le compilateur évaluerait:
(false || true) && (infLoop() || true) = true;
(false || true) =true
(infLoop() || true = true (infLoop isn't called)
mais InfLoop est appelé! C'est pourquoi:
(false || true && infLoop() || true);
1st Comparison.) true && InfLoop() (InfLoop gets called)
2nd Comparison.) False || 1st Comp (will never get here)
3rd Comparison.) 2nd Comp || true; (will never get here)
Precendece définit seulement le groupe de opérations. Dans ce cas, & & est supérieur à ||.
true && false || true && true gets grouped as
(true && false) || (true && true);
Le Compilateur vient le long et détermine dans quel ordre il doit exécuter l'évaluation pour lui donner la meilleure chance de sauver des cycles.
Consider: false && infLoop() || true && true
Precedence Grouping goes like this:
(false && infLoop()) || (true && true)
The compiler then looks at it, and decides it will order the execution in this order:
(true && true) THEN || THEN (false && InfLoop())
C'est kindof un fait..et je ne sais pas comment le démontrer. La préséance est déterminée par les règles de grammaire de la langue. L'optimisation du compilateur est déterminée par chaque compilateur..certains sont meilleurs que d'autres, mais sont libres de réordonner les comparaisons groupées comme elles l'entendent afin de lui donner la "meilleure" chance pour l'exécution la plus rapide avec le moins de comparaisons.
deux faits expliquent le comportement des deux exemples. Premièrement, la priorité de &&
est supérieur à ||
. Deuxièmement, les deux opérateurs logiques utilisent l'évaluation en court-circuit.
la préséance est souvent confondue avec l'ordre d'évaluation, mais elle est indépendante. Une expression peut avoir ses éléments individuels évalués dans n'importe quel ordre, tant que le résultat final est correct. En général, pour certains opérateurs, cela signifie que la valeur à gauche (LHS) peut être évaluée avant ou après après la valeur sur la droite (RHS), aussi longtemps que les deux sont évalués avant l'opérateur lui-même est appliquée.
les opérateurs logiques ont une propriété spéciale: dans certains cas si un côté évalue à une valeur spécifique, alors la valeur de l'opérateur est connue quelle que soit la valeur de l'autre côté. Pour rendre cette propriété utile, le langage C (et par extension tous les langages de type C) A spécifié les opérateurs logiques pour évaluer le LHS avant le RHS, et en outre pour évaluer seulement le membre de droite si sa valeur est pour connaître le résultat de l'opérateur.
Donc, en supposant que les définitions habituelles de l' TRUE
et FALSE
,TRUE && TRUE || FALSE && FALSE
est évaluée à partir de la gauche. Le premier TRUE
ne force pas le résultat de la première &&
, de sorte que le deuxième TRUE
est évalué, puis l'expression TRUE && TRUE
est évalué (à vrai). Maintenant, le ||
connaît son LHS. Encore mieux, son LHS a forcé le résultat du ||
pour être connu, donc il saute évaluation de l'ensemble de ses RHS.
Le même ordre d'évaluation s'applique dans le second cas. Puisque le membre de droite de l' ||
n'est pas pertinent, il n'est pas évalué et aucun appel à infiniteLoop()
est faite.
ce comportement est par conception, et est utile. Par exemple, vous pouvez écrire p && p->next
sachant que l'expression ne tentera jamais de déréférencer un pointeur NULL.
" en cas de court-circuit de l'opérateur || et de court-circuit de l'exécution de la seconde & expression, cela signifie que l'opérateur | a été exécuté avant le second opérateur&&. Cela implique une exécution de gauche à droite pour & & & et || (pas & & de priorité)."
Pas tout à fait.
(xx && yy || zz && qq)
serait évalué comme ceci:
- vérifiez le premier opérateur.
- evaluer
xx && yy
- cochez la case suivante opérateur.
- si l'opérateur suivant est
||
, et la première partie de l'énoncé est vrai, passer le reste. - sinon, Vérifiez l'opérateur suivant après
||
, et de l'évaluer:zz && qq
- Enfin, évaluer
||
.
d'après ce que j'ai compris, C++ est conçu pour lire les choses avant de commencer à les évaluer. Après tout, dans notre exemple, il ne sait pas que nous avons un deuxième &&
vérifier après l' ||
jusqu'à ce qu'il lit dans, sens qu'il a à lire dans le ||
avant qu'il arrive à la deuxième &&
. En tant que tel, si la première partie évaluée à true, il ne fera pas la partie après le ||
, mais si la première partie évalue à false, alors il va faire la première partie, lire dans le ||
, de trouver et d'évaluer la deuxième partie, et la deuxième partie du résultat pour déterminer le résultat final.
concernant votre édition: infiniteLoop () ne sera pas évalué car true | | (peu importe) est toujours true. Utilisez true / (whatever) si quelque chose doit être exécuté.
à propos de la true && true || infiniteLoop() && infiniteLoop()
exemple, aucun des appels de boucle infinie n'est évalué en raison des deux caractéristiques combinées: && a priorité sur ||, et || courts-circuits lorsque le côté gauche est vrai.
si && et || a la même priorité, l'évaluation aurait à faire comme ceci:
((( true && true ) || infiniteLoop ) && infiniteLoop )
(( true || infiniteLoop ) && infiniteLoop )
=> first call to infiniteLoop is short-circuited
(true && infiniteLoop) => second call to infiniteLoop would have to be evaluated
mais en raison de & & 's précédents, l'évaluation va effectivement
(( true && true ) || ( infiniteLoop && infiniteLoop ))
( true || ( infiniteLoop && infiniteLoop ))
=> the entire ( infiniteLoop && infiniteLoop ) expression is short circuited
( true )
concernant le dernier code D'Andrew,
#include <iostream>
using namespace std;
bool infiniteLoop () {
while (true);
return false;
}
int main() {
cout << (true && true || infiniteLoop() && infiniteLoop()) << endl; // true
}
évaluation de court-circuit signifie que les appels à infiniteLoop
sont garantis de ne pas être exécuté.
cependant, c'est intéressant d'une manière perverse parce que le projet C++0x fait la boucle-infinie-qui-fait-rien Comportement Non Défini. Cette nouvelle règle est généralement considérée comme très indésirable et stupide, voire carrément dangereuse, mais elle s'est en quelque sorte glissée dans le projet. En partie à partir de considérations de scénarios de threading, où l'auteur d'un article pensait que cela simplifierait les règles pour quelque chose-ou-autre assez hors de propos.
ainsi, avec un compilateur qui est à la "pointe" de la conformité C++0x, le programme pourrait prendre fin, avec résultat, même si il a exécuté un appel à infiniteLoop
! Bien sûr, avec un tel compilateur, il pourrait aussi produire que phénomène redouté, les démons...
heureusement, comme nous l'avons mentionné, l'évaluation du court-circuit signifie que le les appels sont garantis de ne pas être exécuté.
Cheers & hth.,
Depuis et/ou/vrai/faux est très similaire à */+/1/0 (ils sont mathématiquement équivalentes-ish), il est également vrai:
1 * 1 + 0 * 0 == 1
et plutôt facile à retenir...
Donc oui, il a à voir avec la priorité et court-circuit. Precendence of boolean ops est assez facile si vous le mappez à leurs entiers ops correspondants.
C'est un séquentielle illustration:
(true && true || false && false)
= ((true && true) || (false && false)) // because && is higher precedence than ||,
// like 1 + 2 * 3 = 7 (* higher than +)
= ((true) || (false))
= true
mais notez aussi que si c'est
(true || ( ... ))
puis le côté droit n'est pas évalué, de sorte que toute fonction n'est pas appelée, et l'expression juste retour true
.
La réponse est simple && prend une priorité plus élevée que ||. En outre, le code n'est pas exécuté, car il n'est pas nécessaire de connaître le résultat de l'expression booléenne. Oui, c'est une optimisation du compilateur.