34 votes

La valeur de x * f (x) n'est-elle pas spécifiée si f modifie x?

J'ai regardé un tas de questions au sujet de la séquence de points, et n'ont pas été en mesure de déterminer si l'ordre d'évaluation pour x*f(x) est garanti si l' f modifie x, et est-ce différent pour l' f(x)*x.

Considérer ce code:

#include <iostream>

int fx(int &x) {
  x = x + 1;
  return x;
}

int f1(int &x) {
  return fx(x)*x; // Line A
}

int f2(int &x) {
  return x*fx(x); // Line B
}

int main(void) {
  int a = 6, b = 6;
  std::cout << f1(a) << " " << f2(b) << std::endl;
}

Ce imprime 49 42 g++ 4.8.4 (Ubuntu 14.04).

Je me demande si cela est garanti comportement ou indéterminée.

Plus précisément, dans ce programme, fx est appelée deux fois, avec x=6 deux fois, et retourne 7 fois. La différence est que la Ligne A calcule 7*7 (en prenant la valeur de x après fx rendement), tandis que la Ligne B calcule 6*7 (en prenant la valeur de x avant fx des retours).

Est-ce garanti comportement? Si oui, quelle est la partie de la norme spécifie que cette?

Aussi: Si je change toutes les fonctions à utiliser int *x au lieu de int &x et de faire des changements correspondants aux endroits où ils sont appelés à partir d', je reçois C code qui a les mêmes problèmes. La réponse est toute différente pour le C?

22voto

Maksim Solovjov Points 1309

En termes d'évaluation de la séquence, il est plus facile de penser à des x*f(x) comme si c'était:

operator*(x, f(x));

de sorte qu'il n'y a pas mathématique des idées préconçues sur la façon dont la multiplication est censé fonctionner.

Comme @dan04 obligeamment indiqué, la norme dit:

Section 1.9.15: "Sauf indication contraire, l'évaluation des opérandes des opérateurs individuels et des sous-expressions des expressions individuelles sont séquencé."

Cela signifie que le compilateur est libre d'évaluer ces arguments dans n'importe quel ordre, la séquence de point de operator* appel. La seule garantie est que, avant la operator* est appelé, les deux arguments doivent être évalués.

Dans votre exemple, sur le plan conceptuel, vous pouvez être certain qu'au moins l'un des arguments est de 7, mais vous ne pouvez pas être certain que les deux d'entre eux. Pour moi, ce qui serait suffisant pour l'étiquette de ce comportement indéfini; toutefois, @user2079303 réponse explique bien pourquoi il n'est techniquement pas le cas.

Indépendamment de savoir si le comportement est indéfini ou d'une durée indéterminée, vous ne pouvez pas utiliser une telle expression dans un bien comportés programme.

12voto

user2079303 Points 4916

L'ordre d'évaluation des arguments est pas spécifiée par la norme, de sorte que le comportement que vous voyez n'est pas garanti.

Puisque vous parler de la séquence de points, je vais envisager le c++03 standard qui utilise ce terme, tandis que le plus tard, les normes ont changé de formulation et a abandonné le terme.

ISO/IEC 14882:2003(E) §5 /4:

Sauf indication contraire, l'ordre d'évaluation des opérandes des opérateurs individuels et les sous-expressions des expressions individuelles, et l'ordre dans lequel les effets secondaires prendre place, n'est pas spécifié...


Il y a aussi la discussion à savoir si c'est un comportement indéterminé ou la commande est simplement non spécifié. Le reste de ce paragraphe met en lumière (ou de doute sur ce point.

ISO/IEC 14882:2003(E) §5 /4:

... Entre le précédent et suivant de la séquence de point un scalaire objet doit avoir sa valeur stockée modifié plus d'une fois par l'évaluation d'une expression. En outre, l'état de la valeur doit être accessible uniquement à déterminer la valeur à stocker. Les exigences de ce paragraphe doivent être satisfaites pour chaque admissibles de la commande de la sous-expressions d'une pleine expression, sinon le comportement est indéfini.

x est en effet modifié en f et sa valeur est lue comme un opérande dans la même expression où l' f est appelé. Et il n'est pas spécifié si x lit le modifié ou non modifié par la valeur. Qui pourrait crier Comportement Indéfini! pour vous, mais tenez vos chevaux, parce que la norme indique également:

ISO/IEC 14882:2003(E) §1.9 /17:

... Lors de l'appel d'une fonction (la fonction est sur la ligne), il y a un point de séquence après l'évaluation de tous les arguments de la fonction (le cas échéant) qui a lieu avant l'exécution de toutes les expressions ou des énoncés dans le corps de la fonction. Il y a aussi un point de séquence après la copie d'une valeur renvoyée et avant l'exécution de toutes les expressions en dehors de la fonction 11) ...

Donc, si f(x) est évalué en premier, alors il existe un point de séquence après la copie de la valeur retournée. Donc, la règle ci-dessus à propos de l'UB ne s'applique pas parce que la lecture de x n'est pas entre le précédent et suivant de la séquence de point. L' x opérande aura de la valeur modifiée.

Si x est évalué en premier, alors il existe un point de séquence après avoir évalué les arguments de l' f(x) Encore une fois, la règle sur l'UB ne s'applique pas. Dans ce cas - x opérande aura la non-valeur modifiée.

En résumé, l'ordre n'est pas spécifié, mais il n'y a pas de comportement indéfini. C'est un bug, mais le résultat est prévisible dans une certaine mesure. Le comportement est le même dans les plus tard, les normes, même si le libellé modifié. Je ne vais pas plonger dans ceux, puisqu'elle est déjà bien couvert dans les autres bonnes réponses.


Depuis que vous vous posez sur situation similaire dans le C

C89 (projet) 3.3/3:

Sauf comme il est indiqué par la syntaxe 27 ou autrement spécifié plus tard (pour la fonction d'appel de l'opérateur () , && , || , ?: , et la virgule opérateurs), l'ordre d'évaluation des sous-expressions et l'ordre dans lequel les effets secondaires sont à la fois non spécifié.

L'appel de la fonction d'exception est déjà mentionné ici. Voici le paragraphe qui implique le comportement indéterminé si il n'y avait pas de séquence de points:

C89 (projet) 3.3/2:

Entre le précédent et suivant de la séquence de point d'un objet doit avoir sa valeur stockée modifié plus d'une fois par l'évaluation d'une expression. En outre, l'état de la valeur doit être accessible uniquement à déterminer la valeur à stocker.26

Et voici la séquence de points définis:

C89 (projet) A. 2

Voici la séquence des points décrits dans 2.1.2.3

  • L'appel à une fonction, d'après les arguments ont été évalués (3.3.2.2).

  • ...

  • ... l'expression dans une instruction de retour (3.6.6.4).

Les conclusions sont les mêmes qu'en C++.

7voto

Useless Points 18909

Une note rapide sur quelque chose que je ne vois pas explicitement couverts par les autres réponses:

si l'ordre d'évaluation pour x*f(x) est garanti si l' f modifie x, et est-ce différent pour l' f(x)*x.

Considérer, comme dans la réponse de Maksim

operator*(x, f(x));

maintenant il y a seulement deux façons d'évaluer à la fois les arguments avant l'appel, selon les besoins:

auto lhs = x;        // or auto rhs = f(x);
auto rhs = f(x);     // or auto lhs = x;
    return lhs * rhs

Ainsi, lorsque vous demandez

Je me demande si cela est garanti comportement ou indéterminée.

la norme ne précise pas laquelle de ces deux comportements, le compilateur doit choisir, mais il ne spécifiez ceux sont les seuls valables les comportements.

Donc, ce n'est ni garanti, ni entièrement indéterminée.


Oh, et:

J'ai regardé un tas de questions au sujet de la séquence de points, et n'ont pas été en mesure de déterminer si l'ordre d'évaluation ...

séquence de points sont utilisés dans la norme du langage C du traitement de ce sujet, mais pas dans la norme C++.

5voto

Matt McNabb Points 14273

Dans l'expression x * y, les termes x et y sont non séquencés. C'est l'une des trois possibilités de séquençage de relations, qui sont:

  • A séquencé-avant B: A doit être évalué, avec tous les effets secondaires complet, avant d' B commence evaluationg
  • A et B pour une période indéterminée-séquencé: l'un des deux cas suivants est vrai: A est séquencé en-avant Bou B est séquencé en-avant A. Il est non spécifié lequel de ces deux cas, titulaire d'.
  • A et B non: Il n'y a pas de séquençage relation définie entre A et B.

Il est important de noter que ce sont les paires de relations. On ne peut pas dire "x est non". Nous pouvons seulement dire que les deux opérations sont séquencé à l'égard les uns des autres.

Il est également important que ces relations sont transitifs; et les deux dernières relations sont symétriques.


non spécifié est un terme technique qui signifie que la Norme spécifie un nombre de résultats possibles. C'est différent de comportement indéfini qui signifie que la Norme ne couvre pas les comportements à tous. Voir ici pour plus de lecture.


Le déplacement sur le code x * f(x). C'est identique à l' f(x) * x, parce que, comme discuté ci-dessus, x et f(x) sont des non, avec le respect les uns des autres, dans les deux cas.

Nous en arrivons maintenant au point où plusieurs personnes semblent venir décoller. L'évaluation de l'expression f(x) est séquencé à l'égard x. Cependant, il n'est pas suivi toute les instructions à l'intérieur du corps de la fonction d' f sont également séquencé à l'égard x. En fait, il y a le séquençage des relations liées à tout appel de fonction, et ces relations ne peuvent pas être ignorés.

Voici le texte de C++14:

Lors de l'appel d'une fonction (que ce soit ou non la fonction inline), chaque valeur de calcul et des effets secondaires associés avec n'importe quel argument de l'expression, ou avec le suffixe expression désignant la fonction appelée, est séquencée avant l'exécution de chaque instruction ou de l'expression dans le corps de la fonction appelée. [Note: la Valeur des calculs et des effets secondaires associés avec différentes expressions d'arguments sont séquencé. -la note de fin ] Chaque évaluation dans la fonction appelante (y compris d'autres appels de fonction) qui n'est pas spécifiquement séquencée avant ou après l'exécution du corps de la fonction appelée est pour une période indéterminée séquencéavec l'égard de l'exécution de la fonction appelée.

avec la note de bas de page:

En d'autres termes, la fonction d'exécutions ne pas entrelacer les uns avec les autres.

Le texte en gras indique clairement que les deux expressions:

  • Un: x = x + 1; à l'intérieur d' f(x)
  • B: l'évaluation la première x dans l'expression x * f(x)

leur relation est la suivante: pour une période indéterminée séquencé.

Le texte concernant un comportement indéterminé et le séquençage de l':

Si un effet indésirable sur un scalaire objet est séquencé par rapport à un autre effet secondaire sur le même scalaire objet ou d'une valeur de calcul à l'aide de la valeur de la même scalaire objet, et ils ne sont pas potentiellement concurrentes (1.10), le comportement est indéfini.

Dans ce cas, la relation est pour une période indéterminée séquencé, pas séquencé. Donc, il n'y a pas de comportement indéfini.

Le résultat est plutôt quelconque selon qu' x est séquencée avant de x = x + 1 ou de l'autre manière autour. Donc, il y a que deux résultats possibles, 42 et 49.


Dans le cas où quelqu'un avait des scrupules au sujet de l' x en f(x), le texte suivant s'applique:

Lors de l'appel d'une fonction (que ce soit ou non la fonction inline), chaque valeur de calcul et des effets secondaires associés avec n'importe quel argument de l'expression, ou avec le suffixe expression désignant la fonction appelée, est séquencée avant l'exécution de chaque instruction ou de l'expression dans le corps de la fonction appelée.

Donc, l'évaluation de l' x est séquencée avant de x = x + 1. Ceci est un exemple d'un evlauation qui tombe sous le cas de "spécialement séquencée avant" dans la citation en gras ci-dessus.


Note de bas de page: le comportement est exactement le même en C++03, mais la terminologie est différente. En C++03, nous disons qu'il y a un point de séquence à l'entrée et à la sortie de chaque appel de fonction, donc l'écriture d' x à l'intérieur de la fonction est séparée de la lecture de x en dehors de la fonction d'au moins un point de séquence.

4voto

Jaromír Adamec Points 21

Il faut distinguer:

a) priorité et associativité des opérateurs, qui contrôle l'ordre dans lequel les valeurs des sous-expressions sont combinés par leurs exploitants.

b) La séquence de la sous-expression de l'évaluation. E. g. dans l'expression f(x)/g(x), le compilateur peut évaluer g(x) de la première et de la f(x) par la suite. Néanmoins, la valeur obtenue doit être calculé en divisant sous-valeurs dans le bon ordre, bien sûr.

c) La séquence des effets secondaires de la sous-expressions. Grosso modo, par exemple, le compilateur peut, pour des raisons d'optimisation, décider d'écrire des valeurs aux variables seulement à la fin de l'expression ou de tout autre endroit approprié.

Comme une très grossière approximation, on peut dire qu'au sein d'une seule expression, l'ordre d'évaluation (pas d'associativité, etc.) est plus ou moins indéterminée. Si vous avez besoin d'une commande spécifique de l'évaluation, de décomposer l'expression en série de ce type de déclarations:

int a = f(x); int b = g(x); return a/b;

au lieu de

return f(x)/g(x);

Pour les règles exactes, voir http://en.cppreference.com/w/cpp/language/eval_order

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