9
votes

Commande d'évaluation de la sortie C ++ avec des appels de fonction intégrée

Je suis un TA pour une classe Intro C ++. La question suivante a été posée sur un test la semaine dernière:

Quelle est la sortie du programme suivant: p> xxx pré>

la réponse, à moi et à tous mes collègues, est évidemment : P>

64
27
8


0 commentaires

6 Réponses :


0
votes

Ouais, l'ordre d'évaluation des arguments fonctionnels est "non spécifié" en fonction des normes.

D'où les sorties diffèrent sur différentes plates-formes


6 commentaires

De même STD :: COUT << x << ++ x << x ++; est un comportement non spécifié. Remarque: il n'invoque pas un comportement non défini.


@Prason, non dans ce cas, il fait invoque un comportement non défini, car le changement de x n'est pas bracelé par un point de séquence sur l'autre changement de x < / code> plus.


Ce n'est pas UB parce que "<<" IA un opérateur surchargé (appel de la fonction) et x, x ++, ++ x sont les arguments.in que ce cas soit précis, il s'agit d'un comportement non spécifié car vous ne pouvez pas prédire quel argument sera évalué d'abord. . Un appel de la planification (retour) est en effet un point de séquence.


Il est indéfini. En regardant les subexpressions, nous avons x ++ , ++ x , std :: cou << x , (COUT> << x) << ++ x , that_thing << x ++ . Donc, une commande d'exécution valide est d'abord à exécuter ++ x, puis x ++, puis les différents appels vers opérateur <<< / code>, qui doit être en ordre. Dans cet ordre d'exécution valide, il n'y a pas de point de séquence entre X ++ et ++ x, l'expression total a donc un comportement non défini.


@Prason, c'est à la fois un comportement non spécifié et un comportement indéfini. Mais si vous avez les deux, alors évidemment, vous avez un comportement indéfini. L'ordre d'évaluation des arguments à un appel de fonction est indéterminé. Mais il est indéfini de modifier quelque chose deux fois sans point de séquence intermédiaire. (entre deux appels de fonction dans une expression, il n'y a pas de point de séquence non plus, mais les notes standard que les appels de fonction ne peuvent pas interliser, ce qui séquence efficacement les appels de fonction, même si la commande dans la séquence est indéterminée).


Les points de séquence ont l'inconvénient de ne pas spécifier de manière intrinsèquement une commande - ils divisent simplement une évaluation en deux morceaux non entrelacés. En C ++ 0X, les points de séquence sont partis et nous avons plutôt des "séquences avant" et "indéterminés de manière indéterminée" (ce qui signifie simplement qu'ils ne se mêlent pas, je pense). Les exécutions d'appels de fonction par exemple sont "indéterminées séquencées", tandis que l'évaluation de l'opérande gauche de l'opérateur de la virgule est "séquencée avant" le bon opérande.



15
votes

La norme C ++ ne définit pas quel ordre les sous-expressions d'une expression complète sont évaluées, à l'exception de certains opérateurs qui introduisent une commande (opérateur de la virgule, opérateur ternaire, opérateurs logiques à court-circuit), et le fait que les expressions qui Le maquillage Les arguments / opérandes d'une fonction / opérateur sont tous évalués avant la fonction / opérateur lui-même.

GCC n'est pas obligé de vous expliquer (ou de moi) pourquoi il veut les commander comme cela. Il pourrait s'agir d'une optimisation des performances, ce qui pourrait être parce que le code du compilateur est sorti de quelques lignes plus courtes et plus simples, ce qui pourrait être parce que l'un des codeurs Mingw vous déteste personnellement et veut s'assurer que si vous faites des hypothèses qui ne sont pas des hypothèses t garanti par la norme, votre code passe mal. Bienvenue dans le monde des normes ouvertes: -)

Edit pour ajouter: Le LitB fait un point ci-dessous sur le comportement défini (ONU). La norme indique que si vous modifiez une variable plusieurs fois dans une expression, et s'il existe un ordre d'évaluation valide pour cette expression, de sorte que la variable est modifiée plusieurs fois sans point de séquence entre, alors l'expression a un comportement non défini. Cela ne s'applique pas ici, car la variable est modifiée dans l'appel à la fonction, et il existe un point de séquence au début de tout appel de fonction (même si le compilateur l'allume). Toutefois, si vous avez inlisé manuellement le code: xxx

alors ce serait un comportement indéfini. Dans ce code, il est valable pour le compilateur d'évaluer les trois sous-expressions "x ++", puis les trois appels à POW, puis commencent sur les différents appels à Opérateur <<< / code>. Étant donné que cette commande est valide et n'a aucun point de séquence séparant la modification de x, les résultats sont complètement indéfinis. Dans votre extrait de code, seul l'ordre d'exécution n'est pas spécifié.


3 commentaires

Merci pour l'explication, j'ai pensé que c'était une instance d'une spécification non définie, mais je peux maintenant en être sûr. Et cela peut se tenir d'exemple aux étudiants de pourquoi les effets secondaires et la nidification trop compliquée sont mauvais :)


+1. Et en particulier, notez que le programme n'expose pas un comportement indéfini. x est toujours changé au plus une fois entre deux points de séquence, car les exécutions d'appel de fonction ne peuvent pas se mêler mutuellement. Son comportement n'est pas spécifié et après la déclaration d'expression COUT, la valeur de x doit être 5.


@onebyone: très drôle la partie "haine" ... :)



2
votes

L'ordre dans lequel les paramètres d'appel de la fonction sont évalués sont indéterminés. En bref, vous ne devriez pas utiliser des arguments qui ont des effets secondaires qui affectent la signification et le résultat de la déclaration.


1 commentaires

Vous devrez également expliquer pourquoi (et comment) <<< / code> est un appel de fonction - sinon la réponse peut être difficile à comprendre :)



0
votes

Comme cela a déjà été indiqué, vous avez erré dans la forêt hantée du comportement indéfini. Pour obtenir ce que l'on attend à chaque fois que vous pouvez supprimer les effets secondaires: xxx

ou casser la fonction les appels dans des instructions distinctes: xxx

La deuxième version est probablement meilleure pour un test, car elle les oblige à considérer les effets secondaires.


4 commentaires

Ouais, je leur ai déjà mentionné qu'ils pouvaient voir le comportement attendu en divisant la déclaration de sortie comme celle-là. J'accepterais de ne pas avoir de fonction avec des effets secondaires, mais comme vous avez noté la question est très spécifiquement destinée à tester leur connaissance de tels effets secondaires. Cela et comme le Ta, je ne peux pas changer les questions, expliquez-les simplement :)


Le premier code "correction" ne conduira pas du tout au comportement attendu. L'évaluation des sous-expressions individuelles n'est toujours pas spécifiée et vous avez introduit un problème supplémentaire: vous passez des temporaires à une référence non-Const.


Juste une nit, cela a été mentionné dans d'autres réponses, mais techniquement, cela est un comportement non spécifié non non défini. Un comportement indéfini signifie que tout peut se produire, notamment en formaçant votre disque dur, ou de démarrer Nethack. Un non spécifié signifie qu'il peut évaluer les arguments dans n'importe quel ordre qu'il aime, mais ils doivent tous être évalués avant que la fonction ne soit appelée.


@LitB, le code le plus définitivement réparera parce qu'il rend l'ordre d'évaluation non pertinent. Le "correction" veille à ce que les numéros sont imprimés dans l'ordre attendu, sans contrôler la compilation du code.



10
votes

exactement pourquoi cela a-t-il un comportement non spécifié. strong>

Quand j'ai examiné cet exemple, j'ai estimé que le comportement était bien défini fort> parce que cette expression est réellement courte Pour un ensemble d'appels de fonction. p>

Considérez cet exemple de base: P>

f1()
cout.operator<<(f1())
f2()
cout.operator<<(f1()).operator<<(f2());


1 commentaires

Nice :) J'étais sur le point d'écrire quelque chose de similaire, mais cela m'a fallu un moment pour vérifier ce qui garantit exactement que les appels <<<< / Code> sont effectués en séquence. Comme vous l'avez dit, cela a du sens pour moi, car l'argument de l'objet implicite est juste considéré comme un argument de fonction normal qui est évalué avant le point de séquence de fonctions. Je pensais que cet argument existait simplement dans le but de surcharger, mais il semble que cela ait une importance pour ces jeux latéraux. +1 :)



0
votes

Et c'est pourquoi, chaque fois que vous écrivez une fonction avec un effet secondaire, Dieu tue un chaton!


0 commentaires