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

j'ai regardé un tas de questions concernant les points de séquence, et n'ai pas été en mesure de comprendre si l'ordre d'évaluation pour x*f(x) est garanti si f modifie x , et est ce différent pour f(x)*x .

considérez 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;
}

l'impression 49 42 g++ 4.8.4 (Ubuntu 14.04).

je me demande si c'est un comportement garanti ou indéterminé.

spécifiquement, dans ce programme, fx est appelé deux fois, avec x=6 deux fois, et retourne 7 deux fois. La différence est que la ligne a calcule 7*7 (en prenant la valeur de x après fx ) tandis que la ligne B calcule 6*7 (en prenant la valeur de x avant fx ).

est-ce un comportement garanti? Si oui, quelle partie de la norme spécifie cela?

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

34
demandé sur Navin 2015-09-10 17:22:37
la source

6 ответов

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

operator*(x, f(x));

de sorte qu'il n'y a pas de préconceptions mathématiques sur la façon dont la multiplication est censée fonctionner.

comme @dan04 l'a fait remarquer avec aide, la norme dit:

Section 1.9.15: "sauf indication contraire, les évaluations 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, le point de séquence étant operator* appel. La seule garantie est qu'avant que le operator* soit appelé, les deux arguments doivent être évalués.

dans votre exemple, conceptuellement, vous pourriez être certain qu'au moins un des arguments sera 7, mais vous ne pouvez pas être certain que les deux seront. Pour moi, ce serait suffisant pour étiqueter ceci comportement indéfini; toutefois, @user2079303 réponse explique bien pourquoi il n'est techniquement pas le cas.

que le comportement soit indéterminé ou non, vous ne pouvez pas utiliser une telle expression dans un programme qui se comporte bien.

22
répondu Maksim Solovjov 2015-09-11 11:18:10
la source

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

puisque vous mentionnez les points de séquence, je vais considérer la norme C++03 Qui utilise ce terme alors que les normes plus récentes ont changé le libellé et abandonné le terme.

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

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


il y a aussi une discussion sur la question de savoir s'il s'agit d'un comportement non défini ou si l'ordre est simplement non précisé. Le reste de ce paragraphe met en lumière (ou de doute).

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

... Entre le point de séquence précédent et le point de séquence suivant, la valeur stockée d'un objet scalaire doit être modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur antérieure ne doit être consultée que pour déterminer la valeur à stocker. Les exigences du présent paragraphe doivent être satisfaites pour chaque ordre admissible des sous-expressions d'une expression complète; sinon, le comportement n'est pas défini.

x est en effet modifié dans f et il est la valeur est lue comme un opérande dans la même expression où f est appelé. Et il n'est pas précisé si x lit la valeur modifiée ou non. Qui pourrait crier comportement indéfini! pour vous, mais tenez vos chevaux, parce que le standard dit aussi:

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

... Quand on appelle une fonction (que la fonction soit en ligne ou non), il y a une séquence point après l'évaluation de tous les arguments de fonction (le cas échéant) qui a lieu avant l'exécution de n'importe quelles expressions ou déclarations dans le corps de fonction. Il y a aussi un point de séquence après la copie d'une valeur retournée et avant l'exécution de toute expression en dehors de la fonction 11) ...

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

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

en résumé, l'ordre n'est pas précisé mais il y a pas de comportement non défini . C'est un bug, mais le résultat est prévisible dans une certaine mesure. Le comportement est le même dans les normes ultérieures, même si la formulation a changé. Je ne m'y attarderai pas car il est déjà bien couvert dans d'autres bonnes réponses.


puisque vous posez des questions sur une situation similaire dans C

C89 (projet) 3.3/ 3:

sauf indication contraire 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'exception d'appel de fonction est déjà mentionnée ici. Voici le paragraphe qui implique le comportement non défini s'il n'y avait pas de points de séquence:

C89 (projet) 3.3 / 2:

entre le point de séquence précédent et le point de séquence suivant un objet doit avoir sa valeur stockée modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur antérieure ne doit être consultée que pour déterminer la valeur à stocker. 26

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

C89 (projet) A. 2

les points de séquence sont-ils décrits au 2.1.2.3

  • appel à une fonction, après évaluation des arguments (3.3.2.2).

  • ...

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

les conclusions sont les mêmes que dans C++.

12
répondu user2079303 2015-09-11 12:10:15
la source

une petite note sur quelque chose que je ne vois pas explicitement couvert par les autres réponses:

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

Considèrent, comme dans Maksim la réponse de

operator*(x, f(x));

maintenant, il n'y a que deux façons d'évaluer les deux arguments avant l'appel comme requis:

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

ainsi, quand vous demandez

je me demande si c'est un comportement garanti ou non spécifié.

la norme ne spécifie pas quel de ces deux comportements le compilateur doit choisir, mais elle ne spécifie pas qui sont les seuls comportements valides.

donc, ce n'est ni garanti ni entièrement non spécifié.


Oh, and:

j'ai regardé un tas de questions concernant les points de séquence, et n'ai pas été en mesure de comprendre si l'ordre de l'évaluation ...

sequence points sont utilisés dans le traitement de ceci par la norme de langage C, mais pas dans la norme C++.

7
répondu Useless 2015-09-10 17:52:43
la source

dans l'expression x * y , les Termes x et y sont sans suite . C'est l'une des trois relations séquentielles possibles, qui sont:

  • A séquencé-avant B : A doit être évalué, avec tous les effets secondaires complète, avant B commence évaluation
  • A et B séquence indéterminée : l'un des deux cas suivants est vrai: A est séquencé-avant B , ou B est séquencé-avant A . Il est non spécifié qui de ces deux cas tient.
  • A et B Non séquencé : il n'existe pas de relation séquentielle définie entre A et B .

Il est il est important de noter qu'il s'agit des relations par paire . Nous ne pouvons pas dire x est sans conséquence". Nous pouvons seulement dire que deux opérations ne sont pas suivies l'une de l'autre.

aussi important est que ces relations sont transitive ; 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. Ce comportement est différent de comportement non défini ce qui signifie que la norme ne couvre pas le comportement du tout. Voir ici pour plus de lecture.


on passe au code x * f(x) . Ce qui est identique à f(x) * x , parce que comme discuté ci-dessus, x et f(x) sont sans suite , avec égard les uns des autres, dans les deux cas.

nous arrivons maintenant au point où plusieurs personnes semblent se détacher. L'évaluation de l'expression f(x) n'est pas suivie de par rapport à x . Cependant, il ne pas s'ensuit que toute déclaration à l'intérieur du corps de fonction de f sont également sans conséquence en ce qui concerne x . En fait, il y a des relations de séquençage autour de n'importe quel appel de fonction, et ces relations ne peuvent être ignorées.

voici le texte de C++14:

lors de l'appel d'une fonction (que la fonction soit en ligne ou non), chaque calcul de valeur et effet secondaire associé à une expression d'argument, ou à l'expression postfix désignant la fonction appelée, est séquencé avant exécution de chaque expression ou Instruction dans le corps de la fonction appelée. [Note: La Valeur les calculs et les effets secondaires associés aux différentes expressions d'arguments sont séquencé. -la note de fin de ] chaque évaluation dans la fonction d'appel (y compris les autres appels de fonction) qui n'est pas autrement spécifiquement séquencé avant ou après l'exécution du corps de la fonction appelée est séquencé pour une durée indéterminée avec l'égard de l'exécution de la fonction appelée.

avec note de bas de page:

en d'autres termes, les exécutions de fonction ne s'entrelacent pas.

le texte en caractères gras indique clairement que pour les deux expressions:

  • Un : x = x + 1; à l'intérieur f(x)
  • B : évaluation du premier x dans l'expression x * f(x)

leur relation est: séquence indéterminée .

le texte concernant le comportement et l'enchaînement non définis est le suivant:

si un effet secondaire sur un objet scalaire est sans conséquence par rapport à un autre effet secondaire sur le même objet scalaire ou à un calcul de valeur utilisant la valeur du même objet scalaire, et qu'ils ne sont pas potentiellement simultanée (1.10), le comportement est indéfini.

dans ce cas, la relation est séquencé pour une période indéterminée , non sans suite. Il n'y a donc pas de comportement non défini.

le résultat est plutôt non spécifié selon que x est séquencé avant x = x + 1 ou l'inverse. Il n'y a donc que deux résultats possibles, 42 et 49 .


dans le cas où quelqu'un a eu des scrupules au sujet du x dans f(x) , le texte suivant s'applique:

lors de l'appel d'une fonction (que la fonction soit ou non en ligne), chaque calcul de valeur et effet secondaire associé à une expression d'argument, ou à l'expression postfix désignant la fonction appelée, est séquencé avant exécution de chaque expression ou instruction du corps de la fonction appelée.

donc l'évaluation de ce x est séquencé avant x = x + 1 . Il s'agit d'un exemple d'évaluation qui relève du cas "spécifiquement séquencé avant " dans la citation en caractères gras ci-dessus.


note de bas de page: le comportement était exactement le même en C++03, mais la terminologie était différente. Dans 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 à x à l'intérieur de la fonction est séparée de la lecture de x à l'extérieur de la fonction par au moins un point de séquence.

5
répondu M.M 2017-05-23 14:44:29
la source

vous devez distinguer:

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

b) la séquence d'évaluation de la sous-expression. Par exemple: dans l'expression f(x)/g(x) , le compilateur peut évaluer g(x) d'abord et f(x) ensuite. Néanmoins, la valeur résultante doit être calculée en divisant les sous-valeurs respectives dans le bon ordre: cours.

c) la séquence des effets secondaires des 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 approximation très grossière, on peut dire, que dans une seule expression, l'ordre d'Évaluation (pas associativité etc.) est plus ou moins indéterminée. Si vous avez besoin d'une commande spécifique de évaluation, décomposer l'expression en une série d'énoncés comme celui-ci:

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

4
répondu Jaromír Adamec 2015-09-10 19:03:27
la source

ordre d'évaluation des opérandes de presque tous les opérateurs C++ est indéterminé. Le compilateur peut évaluer les opérandes dans n'importe quel ordre, et peut choisissez un autre ordre lorsque la même expression est évaluée à nouveau

Que l'ordre d'évaluation n'est pas toujours le même d'où vous pouvez obtenir des résultats inattendus.

ordre d'évaluation

1
répondu Praveen 2015-09-10 17:34:10
la source