Priorité de l'opérateur par rapport à L'ordre D'évaluation

les Termes "ordre de priorité de l'opérateur" et "ordre d'évaluation" sont des termes très couramment utilisés dans la programmation et extrêmement importants pour un programmeur. Et, pour autant que je les comprenne, les deux concepts sont étroitement liés; l'un ne peut se passer de l'autre lorsqu'on parle d'expressions.

prenons un exemple simple:

int a=1;  // Line 1
a = a++ + ++a;  // Line 2
printf("%d",a);  // Line 3

maintenant, il est évident que Line 2 conduit à un comportement non défini, depuis points de séquence en C et C++ include:

  1. entre évaluation des opérandes gauche et droite du & & & (logique ET), || (OU logique), et par des virgules opérateur. Par exemple, dans le l'expression *p++ != 0 && *q++ != 0 , tous les effets secondaires de la sous-expression *p++ != 0 sont complétées avant toute tentative d'accéder à q .

  2. entre l'évaluation du premier opérande du ternaire l'opérateur "point d'interrogation" et le deuxième ou troisième opérande. Exemple, dans l'expression a = (*p++) ? (*p++) : 0 il y a un point de séquence après le premier *p++ , ce qui signifie qu'il a déjà ont été incrémentés au moment où le la deuxième instance est exécuté.

  3. à la fin d'une expression complète. Cette catégorie inclut l'expression états (comme la cession a=b; ), le retour les déclarations, les commande des expressions de SI, switch, pendant, ou faire-pendant déclarations, et tous trois expressions dans une déclaration.

  4. avant qu'une fonction ne soit entrée dans un appel de fonction. L'ordre dans lequel les arguments sont évalués n'est pas spécifié, mais ce point de séquence signifie que tous leurs effets secondaires avant la fonction est saisi. Dans l'expression f(i++) + g(j++) + h(k++) , f est appelé avec un paramètre de la valeur originale de i , mais i est incrémenté avant d'entrer dans le corps de f . De même, j et k sont mise à jour avant d'entrer g et h respectivement. Cependant, il n'est pas spécifié dans quel ordre f() , g() , h() sont exécutés, ni dans l'ordre i , j , k sont incrémentés. Les valeurs de j et k dans le corps de f sont donc indéterminé. 3 noter qu'une fonction appel f(a,b,c) n'est pas une utilisation de l' opérateur virgule et de l'ordre de d'évaluation pour a , b , et c est indéterminé.

  5. à une fonction de retour, après que la valeur de retour est copiée dans le l'appel de contexte. (Ce point de séquence est seulement spécifié dans le standard c++ ; il est présent seulement implicitement dans C.)

  6. À la fin d'un initialiseur; par exemple, après l'évaluation de 5 dans la déclaration int a = 5; .

ainsi, en passant par le Point # 3:

à la fin d'une expression complète. Cette catégorie comprend les énoncés d'expression (tels que l'assignation a=b;), les énoncés de retour, les expressions de contrôle de if, switch, while, ou le faire-même si des déclarations, et toutes les trois expressions dans une instruction for.

Line 2 conduit clairement à un comportement non défini. Cela montre comment comportement non défini est étroitement couplé avec points de séquence .

prenons maintenant un autre exemple:

int x=10,y=1,z=2; // Line 4
int result = x<y<z; // Line 5

maintenant il est évident que Line 5 fera la variable result magasin 1 .

maintenant l'expression x<y<z dans Line 5 peut être évaluée comme soit:

x<(y<z) ou (x<y)<z . Dans le premier cas, la valeur de result sera 0 et dans le deuxième cas, result sera 1 . Mais nous savons, quand le Operator Precedence est Equal/Same - Associativity entre en jeu, donc, est évalué comme (x<y)<z .

c'est Ce qui est dit dans ce article MSDN :

la préséance et l'associativité des opérateurs C affectent le groupement et l'évaluation des opérandes dans les expressions. La préséance d'un opérateur n'a de sens que si d'autres opérateurs ayant une préséance supérieure ou inférieure sont présents. Les Expressions avec des opérateurs de priorité supérieure sont évaluées en premier. La préséance peut aussi être décrite par le mot "liant"."Les opérateurs avec une priorité plus élevée sont dits avoir liaison étroite.

maintenant, à propos de l'article ci-dessus:

il mentionne " les Expressions avec des opérateurs de priorité supérieure sont évaluées en premier."

cela peut sembler incorrect. Mais, je pense que l'article ne dit pas quelque chose de mal si nous considérons que () est aussi un opérateur x<y<z est la même que (x<y)<z . Mon raisonnement est que si l'associativité n'est en jeu, alors l'évaluation des expressions complètes deviendrait ambiguë puisque < n'est pas un point de séquence .

aussi, un autre lien que j'ai trouvé dit ceci sur opérateur priorité et associativité :

cette page répertorie les opérateurs C par ordre de priorité (du plus élevé au plus bas). Leur associativité indique dans quel ordre les opérateurs de priorité égale dans un les expressions sont appliquées.

ainsi, le second exemple de int result=x<y<z , nous pouvons voir ici qu'il y a dans toutes les 3 expressions, x , y et z , puisque, la forme la plus simple d'une expression se compose d'une seule constante littérale ou objet. D'où le résultat des expressions x , y , z serait - il rvalues , i.e., 10 , 1 et 2 respectivement. Par conséquent, nous pouvons maintenant interpréter x<y<z comme 10<1<2 .

maintenant, L'associativité ne joue-t-elle pas puisque maintenant nous avons 2 expressions à évaluer, soit 10<1 ou 1<2 et puisque la préséance de l'opérateur est la même, elles sont évaluées de gauche à droite ?

prenant ce dernier exemple comme argument:

int myval = ( printf("Operatorn"), printf("Precedencen"), printf("vsn"),
printf("Order of Evaluationn") );

Maintenant, dans la ci-dessus par exemple, puisque l'opérateur comma a la même priorité, les expressions sont évaluées left-to-right et la valeur de retour du dernier printf() est stockée dans myval .

Dans , de SORTE que/IEC 9899:201x sous J. 1 comportement non spécifié , il mentionne:

L'ordre dans lequel les sous-expressions sont évaluées et l'ordre dans lequel les effets secondaires lieu, sauf comme indiqué pour la fonction appel (), &&, ||, ?: et la virgule les opérateurs (6.5).

Maintenant, je voudrais savoir, serait-il faux de dire:

L'ordre d'évaluation dépend de la priorité des opérateurs, laissant les cas de comportement non spécifié.

je voudrais être corrigé si des erreurs ont été commises dans quelque chose que j'ai dit dans ma question. La raison pour laquelle j'ai posté cette question Est en raison de la confusion créée dans mon esprit par L'Article du MSDN. Est-ce que c'est dans erreur ou pas?

40
demandé sur Jonathan Leffler 2011-03-29 17:16:23

6 réponses

Oui, l'article MSDN est dans l'erreur, au moins à l'égard de la norme C et C++ 1 .

cela dit, Permettez-moi de commencer par une note sur la terminologie: dans la norme C++, ILS (surtout--il y a quelques erreurs) utilisent" évaluation "pour se référer à l'évaluation d'un opérande, et" calcul de valeur " pour se référer à la réalisation d'une opération. Ainsi, lorsque (par exemple) vous faites a + b , chacun de a et b est évalué, alors le la valeur de calcul est réalisé pour déterminer le résultat.

il est clair que l'ordre des calculs de valeur est (principalement) contrôlé par la préséance et l'associativité-le contrôle des calculs de valeur est essentiellement la définition de ce que la préséance et l'associativité sont . Le reste de cette réponse utilise le terme "évaluation" pour désigner l'évaluation des opérandes, et non pour évaluer les calculs.

maintenant, quant à l'ordre d'évaluation étant déterminé par la préséance, non ce n'est pas! C'est aussi simple que cela. Prenons par exemple votre exemple de x<y<z . Selon les règles de l'associativité, cela signifie (x<y)<z . Maintenant, envisagez d'évaluer cette expression sur une machine à empiler. Il est parfaitement permis de faire quelque chose comme cela:

 push(z);    // Evaluates its argument and pushes value on stack
 push(y);
 push(x);
 test_less();  // compares TOS to TOS(1), pushes result on stack
 test_less();

cette évaluation évalue z avant x ou y , mais évalue encore (x<y) , puis compare le résultat de cette comparaison avec z , comme prévu.

résumé: L'ordre d'évaluation est indépendant de l'associativité.

priorité est la même. Nous pouvons changer l'expression à x*y+z , et encore évaluer z avant x ou y :

push(z);
push(y);
push(x);
mul();
add();

résumé: L'ordre d'évaluation est indépendant de la préséance.

quand / si nous ajoutons des effets secondaires, ceci reste le même. Je pense que c'est éducatif de penser aux effets secondaires comme étant effectués par un fil d'exécution séparé, avec un join au point de séquence suivant (par exemple, la fin de l'expression). Donc quelque chose comme a=b++ + ++c; pourrait être exécuté quelque chose comme ceci:

push(a);
push(b);
push(c+1);
side_effects_thread.queue(inc, b);
side_effects_thread.queue(inc, c);
add();
assign();
join(side_effects_thread);

cela montre aussi pourquoi une dépendance apparente n'affecte pas nécessairement l'ordre d'évaluation non plus. Même si a est la cible de la mission, ce encore évaluer a avant évaluer soit b ou c . Notez également que bien que je l'ai écrit comme "thread" ci-dessus, cela pourrait aussi bien être un pool de threads, tous exécutés en parallèle, de sorte que vous ne recevez aucune garantie sur l'ordre d'un incrément par rapport à un autre.

sauf si le matériel avait un support direct (et cheap ) pour la file d'attente sans fil, cela ne serait probablement pas utilisé dans une implémentation réelle (et même alors ce n'est pas très probable). Mettre quelque chose dans une file d'attente de thread-safe aura normalement un peu plus de frais généraux que de faire un seul incrément, il est donc difficile d'imaginer quelqu'un jamais faire cela dans la réalité. Conceptuellement, cependant, l'idée est conforme aux exigences de la NORME: Lorsque vous utilisez une opération de pré/post incrément/décrément, vous spécifiez une opération qui se produira un peu plus tard que cette partie de l'expression est évaluation et sera terminée au prochain point de séquence.

Edit: bien que ce ne soit pas exactement threading, certaines architectures permettent une telle exécution en parallèle. Pour un couple d'exemples, les processeurs Intel Itanium et VLIW comme certains DSPs, permettent à un compilateur de désigner un certain nombre d'instructions à exécuter en parallèle. La plupart des machines VLIW ont une taille d'instruction "paquet" spécifique qui limite le nombre d'instructions exécutées en parallèle. L'Itanium utilise également paquets d'instructions, mais désigne un bit dans un paquet d'instructions pour dire que les instructions du paquet courant peuvent être exécutées en parallèle avec celles du paquet suivant. En utilisant des mécanismes comme celui-ci, vous obtenez des instructions d'exécution en parallèle, tout comme si vous utilisiez plusieurs threads sur des architectures avec lesquelles la plupart d'entre nous sont plus familiers.

résumé: L'ordre d'évaluation est indépendant des dépendances apparentes

toute tentative d'utiliser la valeur avant le prochain point de séquence donne un comportement non défini -- en particulier, "l'autre thread "est (potentiellement) modifier ces données pendant ce temps, et vous avez Non façon de synchroniser l'accès avec l'autre thread. Toute tentative de l'utiliser conduit à un comportement non défini.

juste pour un exemple (certes, maintenant assez tiré par les cheveux), pensez à votre code qui tourne sur une machine virtuelle 64 bits, mais le vrai matériel est un processeur 8 bits. Lorsque vous incrémentez une variable 64-bit, il exécute une séquence quelque chose comme:

load variable[0]
increment
store variable[0]
for (int i=1; i<8; i++) {
    load variable[i]
    add_with_carry 0
    store variable[i]
}

si vous lisez la valeur quelque part au milieu de cette séquence, vous pouvez obtenir quelque chose avec seulement quelques octets modifiés, donc ce que vous obtenez n'est ni l'ancienne valeur ni la nouvelle.

cet exemple précis peut être assez tiré par les cheveux, mais une version moins extrême (par exemple, une variable 64 bits sur une machine 32 bits) est en fait assez commun.

Conclusion

de l'Ordre d'évaluation n' pas dépend de la priorité, l'associativité, ou (forcément) sur l'apparente dépendances. Tenter d'utiliser une variable à laquelle un incrément/décrément pré/post a été appliqué dans n'importe quelle autre partie d'une expression donne vraiment complètement comportement non défini. Bien qu'un véritable accident soit peu probable, vous êtes certainement pas la garantie d'obtenir soit l'ancien ou le nouveau -- vous pourriez trouver quelque chose d'autre entièrement.


1 Je n'ai pas vérifié cet article en particulier, mais un bon nombre d'articles de MSDN parlent de C++ et/ou C++/CLI gérés par Microsoft (ou sont spécifiques à leur implémentation de C++) mais font peu ou rien pour souligner qu'ils ne s'appliquent pas à la norme C ou C++. Cela peut donner l'apparence fausse qu'ils prétendent le les règles qu'ils ont décidé d'appliquer à leurs propres langues réellement s'appliquer à la norme langues. Dans ces cas, les articles ne sont pas techniquement faux -- ils n'ont tout simplement rien à voir avec le standard C ou C++. Si vous tentez d'appliquer ces énoncés à la norme C ou c++, le résultat est faux.

37
répondu Jerry Coffin 2018-10-02 19:58:12

la seule façon de préséance influence l'ordre d'évaluation est qu'il crée des dépendances; sinon, les deux sont orthogonaux. Vous avez des exemples triviaux soigneusement choisis où les dépendances créées par la préséance finit par définir complètement l'ordre d'évaluation, mais ce n'est pas généralement vrai. Et n'oubliez pas non plus que de nombreuses expressions ont deux effets: ils entraînent une valeur, et ils ont des effets secondaires. Ils deux ne sont pas nécessaires pour se produire ensemble, donc même lorsque les dépendances la force d'un ordre spécifique de l'évaluation, c'est seulement de l'ordre de l'évaluation des valeurs; il n'a pas d'effet sur les effets secondaires.

12
répondu James Kanze 2011-03-29 13:46:55

Une bonne façon de voir cela est de prendre l'expression de l'arbre.

si vous avez une expression, disons x+y*z vous pouvez réécrire cela dans un arbre d'expression:

application des règles de priorité et d'associativité:

x + ( y * z )

après avoir appliqué les règles de priorité et d'associativité, vous pouvez sans risque les oublier.

en forme d'arbre:

  x
+
    y
  *
    z

maintenant les feuilles de cette expression sont x , y et z . Cela signifie que vous pouvez évaluer x , y et z dans l'ordre que vous voulez, et aussi que vous pouvez évaluer le résultat de * et x dans n'importe quel ordre.

maintenant, puisque ces expressions n'ont pas d'effets secondaires, vous ne vous en souciez pas vraiment. Mais s'ils le font, la commande peut changer le résultat, et depuis la commande peut être quelque chose compilateur décide, vous avez un problème.

maintenant, les points de séquence apportent un peu d'ordre dans ce chaos. Ils coupent efficacement l'arbre en sections.

x + y * z, z = 10, x + y * z

après priorité et associativité

x + ( y * z ) , z = 10, x + ( y * z)

l'arbre:

      x
    +
        y
      *
        z
  , ------------
      z
    =
      10     
  , ------------
      x
    +
        y
      *
        z   

La partie supérieure de l'arbre sera évalué avant le milieu, et le milieu avant d'en bas.

7
répondu Let_Me_Be 2011-03-29 13:44:20

il mentionne" les Expressions avec des opérateurs de priorité supérieure sont évaluées en premier."

je vais juste répéter ce que j'ai dit ici . En ce qui concerne la norme C et c++, l'article est défectueux. La priorité n'affecte que les jetons sont considérés comme les opérandes de chaque opérateur, mais il n'a pas d'incidence sur l'ordre d'évaluation.

ainsi, le lien explique seulement comment Microsoft a mis en œuvre des choses, pas comment le langage lui-même fonctionne.

4
répondu Prasoon Saurav 2017-05-23 11:54:44

la préséance n'a rien à voir avec l'ordre d'évaluation et vice-versa.

priorité les règles décrivent comment une expression sous-divisée doit être mise entre parenthèses lorsque l'expression mélange différents types d'opérateurs. Par exemple, la multiplication est plus importante que l'addition , donc 2 + 3 x 4 est équivalent à 2 + (3 x 4) , et non (2 + 3) x 4 .

Ordre d'évaluation les règles décrivent l'ordre dans lequel chaque opérande d'une expression est évaluée.

prendre un exemple

y = ++x || --y;   

par la règle de priorité de l'opérateur, il sera mis entre parenthèses comme ( ++/-- a priorité plus élevée que || qui a priorité plus élevée que = ):

y = ( (++x) || (--y) )   

l'ordre d'évaluation de logique ou || stipule que (C11 6.5.14)

l'opérateur || garanties gauche-droite de l'évaluation.

cela signifie que l'opérande de gauche, I. e la sous-expression (x++) sera évaluée en premier. En raison du comportement de court-circuitage; si le premier opérande compare inégal à 0 , le second opérande n'est pas évalué , l'opérande de droite --y ne sera pas évalué bien qu'il soit entre parenthèses avant (++x) || (--y) .

1
répondu haccks 2014-08-28 18:51:29

je pense que c'est seulement le

a++ + ++a

epxression problématique, parce que les

a = a++ + ++a;

correspond au premier en 3. mais ensuite dans le 6. règle: évaluation complète avant l'affectation.

,

a++ + ++a

obtient pour a=1 entièrement évalué à:

1 + 3   // left to right, or
2 + 2   // right to left

le résultat est Le même = 4.

Un

a++ * ++a    // or
a++ == ++a

aurait résultats indéterminés. N'est-ce pas?

-1
répondu mb84 2013-11-05 15:12:35