La norme ANSI mandat les opérateurs logiques à court-circuiter, en C ou C++?
Je suis confus car je me souviens du livre de K&R disant que votre code ne devrait pas dépendre de ces opérations en court-circuit, car elles ne le peuvent pas. Quelqu'un pourrait-il indiquer où, dans la norme, il est dit que les opérations logiques sont toujours court-circuitées? Je suis surtout intéressé par C++, une réponse aussi pour C serait géniale.
Je me souviens également d'avoir lu (je ne me souviens pas où) que l'ordre d'évaluation n'est pas strictement défini, donc votre code ne devrait pas dépendre ni assumer les fonctions dans une expression seraient exécutées dans un ordre spécifique: à la fin d'une déclaration, toutes les fonctions référencées aura été appelé, mais le compilateur est libre de sélectionner l'ordre le plus efficace.
La norme indique-t-elle l'ordre d'évaluation de cette expression?
if( functionA() && functionB() && functionC() ) cout<<"Hello world";
Oui, un ordre de court-circuit et d'évaluation est requis pour les opérateurs ||
et &&
dans les normes C et C++.
La norme C++ dit (il devrait y avoir une clause équivalente dans la norme C):
1.9.18
Dans l'évaluation des expressions suivantes
a && b a || b a ? b : c a , b
en utilisant la signification intégrée des opérateurs dans ces expressions, il y a un point de séquence après l'évaluation de la première expression (12).
En C++, il existe un piège supplémentaire: les courts-circuits ne PAS s'appliquent aux types qui surchargent les opérateurs ||
et &&
.
Note de bas de page 12: Les opérateurs indiqués dans ce paragraphe sont les opérateurs intégrés, comme décrit à l'article 5. Lorsqu'un de ces opérateurs est surchargé (article 13) dans un contexte valide, désignant ainsi un fonction d'opérateur définie par l'utilisateur, l'expression désigne une invocation de fonction et les opérandes forment une liste d'arguments, sans point de séquence implicite entre eux.
Il n'est généralement pas recommandé de surcharger ces opérateurs en C++ sauf si vous avez une exigence très spécifique. Vous pouvez le faire, mais cela peut briser le comportement attendu dans le code d'autres personnes, en particulier si ces opérateurs sont utilisés indirectement via des modèles d'instanciation avec le type surchargeant ces opérateurs.
L'évaluation des courts-circuits et l'ordre d'évaluation sont une norme sémantique obligatoire en C et C++.
Sinon, un code comme celui-ci ne serait pas un idiome courant
char* pChar = 0;
// some actions which may or may not set pChar to something
if ((pChar != 0) && (*pChar != '\0')) {
// do something useful
}
Section 6.5.13 Opérateur ET logique de la spécification C99 (lien PDF) dit
(4). Contrairement à l'opérateur binaire & au niveau du bit, l'opérateur && garantit une évaluation de gauche à droite; il y a un point de séquence après l'évaluation du premier opérande. Si le premier opérande se compare à 0, le deuxième opérande n'est pas évalué.
De même, la section 6.5.14 Logique OR opérateur dit
(4) Contrairement au bit | , l'opérateur || l'opérateur garantit une évaluation de gauche à droite; il y a un point de séquence après l'évaluation du premier opérande. Si le premier opérande est différent de 0, le deuxième opérande n'est pas évalué.
Une formulation similaire peut être trouvée dans les normes C++, vérifiez la section 5.14 dans ce projet de copie . Comme le note le vérificateur dans une autre réponse, si vous remplacez && ou ||, les deux opérandes doivent être évalués car ils deviennent un appel de fonction normal.
Oui, cela l'exige (à la fois l'ordre d'évaluation et le court-circuit). Dans votre exemple, si toutes les fonctions renvoient true, l'ordre des appels provient strictement de functionA puis functionB puis functionC. Utilisé pour cela comme
if(ptr && ptr->value) {
...
}
Idem pour l'opérateur virgule:
// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b());
On dit entre l'opérande gauche et droite de &&
, ||
, ,
et entre le premier et le deuxième/troisième opérande de ?:
(opérateur conditionnel) est un "point de séquence". Tout effet secondaire est évalué complètement avant ce point. Donc, c'est sûr:
int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1
Notez que l'opérateur virgule ne doit pas être confondu avec la virgule syntaxique utilisée pour séparer les choses:
// order of calls to a and b is unspecified!
function(a(), b());
La norme C++ indique dans 5.14/1
:
L'opérateur && regroupe de gauche à droite. Les opérandes sont tous deux implicitement convertis en type bool (article 4). Le résultat est vrai si les deux opérandes sont vrais et faux sinon. Contrairement à &, && garantit une évaluation de gauche à droite: le deuxième opérande n'est pas évalué si le premier opérande est faux.
Et en 5.15/1
:
Le || groupes d'opérateurs de gauche à droite. Les opérandes sont tous deux implicitement convertis en bool (article 4). Il retourne vrai si l'un de ses opérandes est vrai et faux sinon. Contrairement à |, || garantit une évaluation de gauche à droite; de plus, le deuxième opérande n'est pas évalué si le premier opérande est évalué à vrai.
Il dit pour les deux à côté de ceux-ci:
Le résultat est un bool. Tous les effets secondaires de la première expression, à l'exception de la destruction des temporaires (12.2), se produisent avant l'évaluation de la deuxième expression.
En plus de ça, 1.9/18
dit
Dans l'évaluation de chacune des expressions
a && b
a || b
a ? b : C
a , b
en utilisant la signification intégrée des opérateurs dans ces expressions (5.14, 5.15, 5.16, 5.18), il y a un point de séquence après l'évaluation de la première expression.
Directement du bon vieux K&R:
C garantit que
&&
et||
sont évalués de gauche à droite - nous verrons bientôt les cas où cela compte.
Soyez très très prudent.
Pour les types fondamentaux, ce sont des opérateurs de raccourci.
Mais si vous définissez ces opérateurs pour votre propre classe ou types d'énumération, ils ne sont pas des raccourcis. En raison de cette différence sémantique dans leur utilisation dans ces différentes circonstances, il est recommandé de ne pas définir ces opérateurs.
Pour le operator &&
et operator ||
pour les types fondamentaux, l'ordre d'évaluation est de gauche à droite (sinon un raccourci serait difficile :-) Mais pour les opérateurs surchargés que vous définissez, ce sont essentiellement du sucre syntaxique pour définir une méthode et donc l'ordre d'évaluation des paramètres n'est pas défini .
Si vous faites confiance à Wikipedia:
[
&&
et||
] sont sémantiquement distincts des opérateurs bit à bit & et | car ils n'évalueront jamais l'opérande droit si le résultat peut être déterminé à partir de la gauche seule
Votre question se résume à priorité de l'opérateur C++ et à l'associativité. Fondamentalement, dans les expressions avec plusieurs opérateurs et sans parenthèses, le compilateur construit l'arborescence des expressions en suivant ces règles.
Pour la priorité, lorsque vous avez quelque chose comme A op1 B op2 C
, Vous pouvez grouper les choses comme (A op1 B) op2 C
Ou A op1 (B op2 C)
. Si op1
A une priorité plus élevée que op2
, Vous obtiendrez la première expression. Sinon, vous obtiendrez le deuxième.
Pour l'associativité, lorsque vous avez quelque chose comme A op B op C
, Vous pouvez à nouveau regrouper les fins comme (A op B) op C
Ou A op (B op C)
. Si op
a quitté l'associativité, on se retrouve avec la première expression. S'il a une bonne associativité, on se retrouve avec le second. Cela fonctionne également pour les opérateurs au même niveau de priorité.
Dans ce cas particulier, &&
A une priorité plus élevée que ||
, Donc l'expression sera évaluée comme (a != "" && it == seqMap.end()) || isEven
.
L'ordre lui-même est "de gauche à droite" sur la forme d'arbre d'expression. Nous allons donc d'abord évaluer a != "" && it == seqMap.end()
. Si c'est vrai, l'expression entière est vraie, sinon on passe à isEven
. La procédure se répète récursivement à l'intérieur de la sous-expression gauche bien sûr.
Quelques détails intéressants, mais le concept de priorité a ses racines dans la notation mathématique. La même chose se produit dans a*b + c
, Où *
A une priorité plus élevée que +
.
Encore plus intéressant/obscur, pour une expression non accentuée A1 op1 A2 op2 ... opn-1 An
, Où tous les opérateurs ont la même priorité, le nombre d'arbres d'expression binaire que nous pourrions former est donné par ce qu'on appelle nombres catalans . Pour les grands n
, ceux-ci se développent extrêmement rapidement. ré