47 votes

Priorité de l'opérateur vs ordre d'évaluation

Ces 2 sont très couramment utilisés termes de programmation et c'est extrêmement important pour un programmeur de savoir. Et comme je le comprends ces 2 concepts sont étroitement liés, l'un ne peut pas faire sans l'autre quand 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 qu' Line 2 conduit à un Comportement Indéfini, puisque la Séquence de points en C et C++ comprennent:

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

  2. Entre l'évaluation de la première opérande de l'ternaire "point d'interrogation" de l'opérateur et de l' deuxième ou troisième opérande. Par exemple, dans l'expression a = (*p++) ? (*p++) : 0 il y a un point de séquence après le premier *p++, sens qu'il a déjà été incrémenté par le temps de l' la deuxième instance est exécuté.

  3. À la fin d'une pleine expression. Cette catégorie comprend expression états (comme la cession a=b;), de retour des états, la le contrôle des expressions de if, switch, tout ou-même si des déclarations, et toutes les trois expressions dans une instruction for.

  4. Avant qu'une fonction est entré 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 l'ensemble de leurs effets secondaires avant la fonction est entré. Dans l'expression f(i++) + g(j++) + h(k++), f est appelée avec un paramètre de la valeur initiale de i, mais i est incrémenté avant d'entrer dans le corps de f. De même, j et k sont mis à jour avant d'entrer dans g et h respectivement. Cependant, il n'est pas spécifié dans l'ordre de f(), g(), h() sont exécutées, ni dans quel ordre i, j, k est incrémenté. Les valeurs de j et k dans le corps de f est donc undefined.3 Notez qu'une fonction appel de f(a,b,c) n'est pas une utilisation de l' opérateur virgule et de l'ordre de évaluation pour a, b, et c est non spécifié.

  5. Au retour de la fonction, après le retour de la valeur est copiée dans le l'appel de contexte. (De ce point de séquence n'est spécifié dans la norme C++; elle n'est présente que de manière implicite 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 n ° 3:

À la fin d'une pleine expression. Cette catégorie comprend l'expression des états (comme l'affectation a=b;), de retour des états, le contrôle des expressions de if, switch, while, ou le faire-même si des déclarations, et toutes les trois expressions dans une instruction for.

Line 2 clairement conduit à un Comportement Indéfini. Cela montre à quel Comportement Indéfini est étroitement associé à la Séquence de Points.

Prenons maintenant un autre exemple:

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

Maintenant, son évident que Line 5 fera la variable result stocker 1.

Maintenant, l'expression x<y<z en Line 5 peut être évalué comme suit:

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

C'est ce qui est dit dans cet Article MSDN:

La priorité et associativité des opérateurs de C affectent le regroupement et l'évaluation des opérandes dans les expressions. Un opérateur de priorité n'a de sens que si d'autres opérateurs plus ou moins élevés de précédence sont présents. Expressions avec plus de précédence des opérateurs sont évalués en premier. La priorité peut aussi être décrit par le mot "contraignantes". Les opérateurs avec une priorité plus élevée sont dit d'avoir le resserrement de liaison.

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

Il mentionne les "Expressions avec plus de précédence des opérateurs sont évalués en premier."

Il peut sembler incorrecte. Mais, je pense que l'article n'est pas de dire quelque chose de mal si l'on considère qu' () est également un opérateur x<y<z est le même que (x<y)<z. Mon raisonnement est que si l'associativité n'est en jeu, alors les expressions complètes évaluation devient ambigu car < n'est pas un Point de Séquence.

Aussi, un autre lien que j'ai trouvé dit cela à Priorité et Associativité des opérateurs:

Cette page répertorie les opérateurs de C dans l'ordre de priorité (de la plus haute à la plus basse). Leur associativité indique dans quel ordre les opérateurs de même priorité, dans une expression sont appliquées.

Donc, en prenant, le deuxième exemple d' int result=x<y<z, nous pouvons voir ici qu'il y a en tout 3 expressions, x, y et z, depuis, la forme la plus simple d'une expression est composée d'une seule constante littérale ou d'un objet. Donc le résultat de ces expressions, en x, y, z y serait rvalues, c'est à dire, 10, 1 et 2 respectivement. Donc, maintenant nous pouvons interpréter x<y<z comme 10<1<2.

Maintenant, n'est-ce pas l'Associativité entrent en jeu depuis maintenant, nous avons 2 expressions à évaluer, soit 10<1 ou 1<2 , et puisque la priorité de l'opérateur est le même, ils sont évalués de gauche à droite?

La prise de ce dernier exemple que mon argument:

int myval = ( printf("Operator\n"), printf("Precedence\n"), printf("vs\n"),
printf("Order of Evaluation\n") );

Maintenant, dans l'exemple ci-dessus, depuis l' comma opérateur a même ordre de priorité, les expressions sont évaluées left-to-right et la valeur de retour de la dernière printf() est stocké dans myval.

Dans SO/IEC 9899:201x en vertu de J. 1 comportement Quelconque , il mentionne:

L'ordre dans lequel les sous-expressions sont évaluées et l'ordre dans lequel les effets secondaires prendre place, sauf tel que spécifié pour la fonction appel (), &&, ||, ?:, et la virgule les opérateurs (6.5).

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

Afin de l'É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é faits dans quelque chose je l'ai dit dans ma question. La raison que j'ai posté cette question est en raison de la confusion créée dans mon esprit par l'Article MSDN. Est-il dans l'Erreur ou pas?

48voto

Jerry Coffin Points 237758

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

Maintenant, quant à l'évaluation de l'ordre étant déterminé par priorité, non, il n'est pas! C'est aussi simple que cela. Juste pour exemple, prenons votre exemple de l' x<y<z. Selon les règles d'associativité, cette analyse comme (x<y)<z. Maintenant, pensez à évaluer cette expression sur une pile de linge. Il est parfaitement admissible qu'un à faire quelque chose comme ceci:

 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 évalue z avant x ou y, mais encore évalue (x<y), puis de comparer le résultat de cette comparaison à l' z, tout comme il est censé le faire.

Résumé: de l'Ordre d'évaluation est indépendante de l'associativité.

La priorité est de la même façon. On peut changer l'expression d' x*y+z, et encore évaluer z avant x ou y:

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

Résumé: de l'Ordre d'évaluation est indépendante de la préséance.

Quand/si on ajoute des effets secondaires, cela reste la même. Je pense que c'est l'éducation à penser à des effets secondaires comme étant effectuées par un autre thread d'exécution, avec un join à la séquence suivante (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 apparente de dépendance n'a pas nécessairement d'incidence sur l'ordre d'évaluation. Même si a est la cible de la mission, c'est encore évalue a avant l'évaluation, soit b ou c. Notez également que, bien que je l'ai écrit comme "fil" ci-dessus, cela pourrait tout aussi bien être un pool de threads s'exécutant en parallèle, de sorte que vous n'obtenez pas de garantie sur l'ordre d'un incrément par rapport à une autre, soit.

À moins que le matériel avait direct (et pas cher) le support pour les threads, les files d'attente, ce ne serait probablement pas être utilisé dans une réelle mise en œuvre (et même alors, il est très peu probable). Mettre quelque chose dans un thread-safe file d'attente aura normalement un peu plus de généraux que de le faire une seule fois, de sorte qu'il est difficile d'imaginer qu'on ait jamais fait cela dans la réalité. Sur le plan conceptuel, cependant, l'idée est s'adapte aux exigences de la norme: lorsque vous utilisez un pré/post incrémentation/décrémentation de l'opération, vous êtes en spécifiant une opération qui va se produire peu de temps après que la partie de l'expression est évaluée, et prendra fin lors de la prochaine séquence de point.

Edit: si ce n'est pas exactement le filetage, certaines architectures ne permettent une telle exécution parallèle. Pour un couple d'exemples, l'Itanium d'Intel et les processeurs VLIW comme certains Dsp, de permettre à un compilateur pour désigner un certain nombre d'instructions qui seront exécutées en parallèle. La plupart des VLIW machines ont une instruction spécifique "paquet" de taille qui limite le nombre d'instructions exécutées en parallèle. L'Itanium utilise également des paquets d'instructions, mais désigne un peu dans une instruction de paquets-à-dire que les instructions contenues dans le paquet actuel peut être exécutée en parallèle avec ceux du paquet suivant. À l'aide de mécanismes comme cela, vous obtenez des instructions à exécuter en parallèle, tout comme si vous avez utilisé plusieurs threads sur des architectures avec laquelle la plupart d'entre nous sont plus familiers.

Résumé: de l'Ordre d'évaluation est indépendante de l'apparente dépendances

Toute tentative à l'aide de la valeur avant le prochain point de séquence donne un comportement non défini-en particulier, "l'autre "thread" est (potentiellement) la modification des données au cours de ce temps, et vous avez aucun moyen de les synchroniser l'accès avec les autres fil. Toute tentative d'utilisation elle conduit à un comportement indéfini.

Juste pour un (certes, maintenant, plutôt farfelue), par exemple, pensez à votre code en cours d'exécution sur une machine virtuelle 64 bits, mais le matériel réel est une 8-bits processeur. Lorsque vous incrémenter une version 64-bit de la variable, il exécute une séquence de 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 pourriez obtenir quelque chose avec uniquement quelques octets modifiés, de sorte que vous obtenez n'est ni l'ancien ni le nouveau.

Cet exemple précis peut être assez farfelue, mais une version moins extrême (par exemple, un 64-bit de la variable sur une machine 32 bits) est en fait assez commun.

Conclusion

L'ordre d'évaluation n'est pas dépendent de priorité, l'associativité, ou (forcément) sur l'apparente dépendances. Tentative d'utilisation d'une variable à laquelle un pré/post incrémentation/décrémentation a été appliquée dans toute autre partie de l'expression donne vraiment complètement un comportement indéfini. Alors que d'une réelle panne est peu probable, vous n'ê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 tout à fait quelques articles MSDN parler de Microsoft Managed C++ et/ou C++/CLI, mais faire peu ou rien à signaler qu'ils ne s'appliquent pas à la norme de C ou de C++. Cela peut donner la fausse impression qu'ils sont en prétendant 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 rien à voir avec la norme C ou C++. Si vous essayez d'appliquer ces déclarations à la norme C ou C++, le résultat est faux.

12voto

James Kanze Points 96599

La seule façon de priorité influence de l'ordre d'évaluation est qu'il crée des dépendances; sinon, les deux sont orthogonaux. Vous avez soigneusement choisis trivial exemples où les dépendances créées par la priorité ne finissent par complètement la définition de 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. Ces deux ne sont pas tenus de se produire ensemble, de sorte que même lorsque les dépendances la force d'une commande 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.

7voto

Let_Me_Be Points 16797

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 que dans une arborescence d'expression:

L'application de la priorité et l'associativité des règles:

x + ( y * z )

Après l'application de la priorité et l'associativité des règles, vous pouvez les oublier.

Dans la forme de l'arbre:

  x
+
    y
  *
    z

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

Maintenant, depuis que ces expressions n'ont pas d'effets secondaires, vous ne m'en soucie pas vraiment. Mais s'ils le font, la commande peut changer le résultat, et depuis la commande peut être tout ce que le compilateur décide, vous avez un problème.

Maintenant, la séquence des points d'apporter un peu d'ordre dans ce chaos. Ils ont effectivement couper l'arbre en sections.

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

après la priorité et l'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.

4voto

Prasoon Saurav Points 47488

Il mentionne les "Expressions avec plus de précédence des opérateurs sont évalués en premier."

Je vais juste répéter ce que j'ai dit ici. Standard C et C++ sont concernés que 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.

Donc, le lien n'explique comment Microsoft a mis en place des choses, pas la façon dont le langage lui-même fonctionne.

-1voto

mb84 Points 413

Je pense que ce n'est que le

 a++ + ++a
 

epxression problématique, car

 a = a++ + ++a;
 

correspond d'abord à 3. mais ensuite à la règle 6.: terminer l'évaluation avant l'affectation.

Donc,

 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 des résultats indéfinis. N'est-ce pas?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X