Comportement non défini et points de séquence rechargés

84

Considérez ce sujet comme une suite du sujet suivant:

Épisode précédent
Comportement et points de séquence non définis

Revisitons cette expression drôle et alambiquée (les phrases en italique sont tirées du sujet ci-dessus * sourire *):

i += ++i;

Nous disons que cela invoque un comportement indéfini. Je présume que lorsque vous dites cela, nous supposons implicitement que le type de iest l'un des types intégrés.

Que faire si le type de iest un type défini par l'utilisateur? Disons que son type est Indexdéfini plus loin dans ce post (voir ci-dessous). Invoquerait-il toujours un comportement indéfini?

Si oui, pourquoi? N'est-ce pas équivalent à l'écriture i.operator+=(i.operator++());ou même syntaxiquement plus simple i.add(i.inc());? Ou, invoquent-ils eux aussi un comportement indéfini?

Si non, pourquoi pas? Après tout, l'objet iest modifié deux fois entre des points de séquence consécutifs. Rappelez-vous la règle empirique: une expression ne peut modifier la valeur d'un objet qu'une seule fois entre "points de séquence consécutifs" . Et s'il i += ++is'agit d'une expression, elle doit invoquer un comportement indéfini. Si tel est le cas, ses équivalents i.operator+=(i.operator++());et i.add(i.inc());doit également invoquer un comportement indéfini qui semble être faux! (pour autant que je sache)

Ou i += ++in'est-ce pas une expression pour commencer? Si oui, qu'est-ce que c'est et quelle est la définition de l' expression ?

S'il s'agit d'une expression, et en même temps, son comportement est également bien défini, cela implique que le nombre de points de séquence associés à une expression dépend en quelque sorte du type d'opérandes impliqués dans l'expression. Ai-je raison (même partiellement)?


Au fait, qu'en est-il de cette expression?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

Vous devez également en tenir compte dans votre réponse (si vous connaissez son comportement avec certitude). :-)


Est

++++++i;

bien défini en C ++ 03? Après tout, c'est ça,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};
Nawaz
la source
13
+1 grande question, qui a inspiré de bonnes réponses. Je pense que je devrais dire que c'est toujours un code horrible qui devrait être remanié pour être plus lisible, mais vous le savez probablement de toute façon :)
Philip Potter
4
@ Quelle est la question: qui a dit que c'était la même chose? ou qui a dit que ce n'est pas la même chose? Cela ne dépend-il pas de la manière dont vous les mettez en œuvre? (Remarque: je suppose que le type de sest un type défini par l'utilisateur!)
Nawaz
5
Je ne vois aucun objet scalaire modifié deux fois entre deux points de séquence ...
Johannes Schaub - litb
3
@Johannes: alors il s'agit d' objet scalaire . Qu'Est-ce que c'est? Je me demande pourquoi je n'en ai jamais entendu parler auparavant. Peut-être, parce que les tutoriels / C ++ - faq ne le mentionnent pas, ou ne le mettent pas en valeur? Est-ce différent des objets de type intégré ?
Nawaz le
3
@Phillip: De toute évidence, je ne vais pas écrire un tel code dans la vraie vie; en fait, aucun programmeur sensé ne l'écrira. Ces questions sont généralement conçues pour que nous puissions mieux comprendre toute l'activité du comportement indéfini et des points de séquence! :-)
Nawaz

Réponses:

48

Cela ressemble au code

i.operator+=(i.operator ++());

Fonctionne parfaitement bien en ce qui concerne les points de séquence. La section 1.9.17 de la norme ISO C ++ dit ceci à propos des points de séquence et de l'évaluation des fonctions:

Lors de l'appel d'une fonction (que la fonction soit en ligne ou non), il existe un point de séquence après l'évaluation de tous les arguments de fonction (le cas échéant) qui a lieu avant l'exécution de toute expression ou instruction dans le corps de la fonction. Il existe également 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.

Cela indiquerait, par exemple, que le i.operator ++()paramètre operator +=a un point de séquence après son évaluation. En bref, comme les opérateurs surchargés sont des fonctions, les règles de séquencement normales s'appliquent.

Excellente question, au fait! J'aime beaucoup la façon dont vous me forcez à comprendre toutes les nuances d'un langage que je pensais déjà connaître (et pensais que je pensais savoir). :-)

templatetypedef
la source
12

http://www.eelis.net/C++/analogliterals.xhtml Les littéraux analogiques me viennent à l'esprit

  unsigned int c = ( o-----o
                     |     !
                     !     !
                     !     !
                     o-----o ).area;

  assert( c == (I-----I) * (I-------I) );

  assert( ( o-----o
            |     !
            !     !
            !     !
            !     !
            o-----o ).area == ( o---------o
                                |         !
                                !         !
                                o---------o ).area );
Antidépresseur industriel
la source
Il y avait une question, est ++++++ i; bien défini en C ++ 03?
Antidépresseur industriel le
11

Comme d'autres l'ont dit, votre i += ++iexemple fonctionne avec le type défini par l'utilisateur puisque vous appelez des fonctions, et les fonctions comprennent des points de séquence.

D'un autre côté, il a[++i] = in'est pas si chanceux de supposer que ac'est votre type de tableau de base, ou même celui défini par l'utilisateur. Le problème que vous avez ici est que nous ne savons pas quelle partie de l'expression contenant iest évaluée en premier. Il se peut que ce ++isoit évalué, transmis à operator[](ou à la version brute) afin de récupérer l'objet là-bas, puis la valeur de iest passée à cela (ce qui est après l'incrémentation de i). D'autre part, peut-être que ce dernier côté est évalué en premier, stocké pour une affectation ultérieure, puis la ++ipièce est évaluée.

Edward étrange
la source
donc ... le résultat est-il donc non spécifié plutôt que UB, puisque l'ordre d'évaluation des expressions n'est pas spécifié?
Philip Potter
@Philip: unspecified signifie que nous nous attendons à ce que le compilateur spécifie le comportement, alors qu'undefined n'impose aucune obligation de ce type. Je pense que ce n'est pas défini ici, pour laisser plus de place aux compilateurs pour les optimisations.
Matthieu M.
@Noah: J'ai également publié une réponse. S'il vous plaît vérifier et laissez-moi savoir ce que vous en pensez. :-)
Nawaz
1
@Philip: le résultat est UB, en raison de la règle du 5/4: "Les exigences de ce paragraphe doivent être remplies pour chaque ordre autorisé des sous-expressions d'une expression complète, sinon le comportement n'est pas défini.". Si tous les ordres autorisés avaient des points de séquence entre la modification ++iet la lecture du iRHS de l'affectation, alors l'ordre serait non spécifié. Étant donné que l'un des ordres autorisés effectue ces deux opérations sans point de séquence intermédiaire, le comportement n'est pas défini.
Steve Jessop
1
@Philip: Il ne définit pas simplement un comportement non spécifié comme un comportement non défini. Encore une fois, si la plage de comportements non spécifiés en inclut certains qui ne sont pas définis, alors le comportement global n'est pas défini. Si la plage de comportements non spécifiés est définie dans toutes les possibilités, alors le comportement global n'est pas spécifié. Mais vous avez raison sur le deuxième point, je pensais à un utilisateur défini aet intégré i.
Steve Jessop
8

Je pense que c'est bien défini:

À partir du projet de norme C ++ (n1905) §1.9 / 16:

"Il existe également un point de séquence après la copie d'une valeur renvoyée et avant l'exécution de toute expression en dehors de la fonction13). Plusieurs contextes en C ++ provoquent l'évaluation d'un appel de fonction, même si aucune syntaxe d'appel de fonction correspondante n'apparaît dans l'unité de traduction. [ Exemple : l'évaluation d'une nouvelle expression appelle une ou plusieurs fonctions d'allocation et de constructeur; voir 5.3.4. Pour un autre exemple, l'invocation d'une fonction de conversion (12.3.2) peut survenir dans des contextes dans lesquels aucune syntaxe d'appel de fonction n'apparaît. - fin exemple ] Les points de séquence à l'entrée de fonction et à la sortie de fonction (comme décrit ci-dessus) sont des caractéristiques des appels de fonction évalués, quelle que soit la syntaxe de l'expressionqui appelle la fonction pourrait être. "

Notez la partie que j'ai mise en gras. Cela signifie qu'il y a bien un point de séquence après l'appel de fonction d'incrémentation ( i.operator ++()) mais avant l'appel d'assignation composée ( i.operator+=).

Matthew Flaschen
la source
6

Bien. Après avoir parcouru les réponses précédentes, j'ai repensé à ma propre question, en particulier à cette partie à laquelle seul Noah a tenté de répondre mais je ne suis pas complètement convaincu avec lui.

a[++i] = i;

Cas 1:

Si aest un tableau de type intégré. Alors ce que Noé a dit est correct. C'est,

a [++ i] = i n'est pas si chanceux en supposant que a est votre type de tableau de base, ou même un utilisateur défini . Le problème que vous avez ici est que nous ne savons pas quelle partie de l'expression contenant i est évaluée en premier.

a[++i]=iInvoque donc un comportement indéfini, ou le résultat n'est pas spécifié. Quoi qu'il en soit, ce n'est pas bien défini!

PS: dans la citation ci-dessus, barré est bien sûr à moi.

Cas 2:

Si aest un objet de type défini par l'utilisateur qui surcharge le operator[], il y a là encore deux cas.

  1. Si le type de retour de la operator[]fonction surchargée est un type intégré, alors à nouveau a[++i]=iinvoque undefined-behavior ou le résultat n'est pas spécifié.
  2. Mais si le type de retour de surcharge operator[]fonction est de type défini par l' utilisateur, le comportement a[++i] = iest bien défini (pour autant que je comprends), puisque dans ce cas a[++i]=iest équivalent à l' écriture a.operator[](++i).operator=(i);qui est la même que, a[++i].operator=(i);. C'est-à-dire que l'affectation operator=est invoquée sur l' objet retourné de a[++i], qui semble être très bien défini, car au moment où les a[++i]retours ++iont déjà été évalués, l' objet retourné appelle la operator=fonction en lui passant la valeur mise à jour de ien argument. Notez qu'il existe un point de séquence entre ces deux appels . Et la syntaxe garantit qu'il n'y a pas de concurrence entre ces deux appels, etoperator[]serait invoqué en premier, et consécutivement, l'argument qui y est ++ipassé serait également évalué en premier.

Considérez cela comme someInstance.Fun(++k).Gun(10).Sun(k).Tun();dans lequel chaque appel de fonction consécutif renvoie un objet d'un type défini par l'utilisateur. Pour moi, cette situation ressemble plus à ceci:, eat(++k);drink(10);sleep(k)car dans les deux situations, il existe un point de séquence après chaque appel de fonction.

Corrigez-moi si j'ai tort, s'il-vous plait. :-)

Nawaz
la source
1
@Nawaz k++et nek sont pas séparés par des points de séquence. Ils peuvent tous deux être évalués avant l'un Sunou l' autre Fun. Le langage exige seulement que ce Funsoit évalué avant Sun, pas que Funles arguments de soient évalués avant Sunles arguments de. J'explique en quelque sorte la même chose sans pouvoir fournir de référence, donc nous n'allons pas progresser à partir d'ici.
Philip Potter
1
@Nawaz: car il n'y a rien qui définit un point de séquence les séparant. Il y a des points de séquence avant et après l' Sunexécution, mais Funl'argument de ++kpeut être évalué avant ou après cela. Il y a des points de séquence avant et après l' Funexécution, mais Sunl'argument de kpeut être évalué avant ou après cela. Par conséquent, un cas possible est que les deux ket ++ksont évalués avant l'un Sunou l' autre ou Funsont évalués, et donc les deux sont avant les points de séquence d'appel de fonction, et il n'y a donc pas de point de séquence séparant ket ++k.
Philip Potter
1
@Philip: Je le répète: en quoi cette situation est-elle différente eat(i++);drink(10);sleep(i);? ... même maintenant, vous pourriez dire i++peut être évalué avant ou après cela?
Nawaz
1
@Nawaz: comment être plus clair? Dans l'exemple Fun / Sun, il n'y a pas de point de séquence entre ket ++k. Dans l'exemple manger / boire, il y a un point de séquence entre iet i++.
Philip Potter
3
@Philip: cela n'a aucun sens. Entre Fun () et Sun () existe un point de séquence, mais entre leur argument n'existe pas de points de séquence. C'est comme dire, entre eat()et sleep()existe un ou plusieurs points de séquence, mais entre les arguments, il n'y en a même pas un. Comment les arguments de deux appels de fonction séparés par des points de séquence peuvent-ils appartenir aux mêmes points de séquence?
Nawaz le