Est-ce code bien défini?

ce code est tiré d'une discussion en cours sur ici .

someInstance.Fun(++k).Gun(10).Sun(k).Tun();

ce code est-il bien défini? Est-ce que ++k en Fun () est évalué avant k en Sun ()?

et si k est un type défini par l'Utilisateur, pas un type intégré? Et de quelle manière l'ordre d'appel de fonction ci-dessus est différent de ceci:

eat(++k);drink(10);sleep(k);

autant que je sache, dans les deux cas, il existe une séquence point après chaque appel de fonction . Si c'est le cas, pourquoi le premier cas n'est-il pas aussi bien défini que le second?

la Section 1.9.17 de la norme C++ ISO dit ceci au sujet des points de séquence et de l'évaluation de la fonction:

Lors de l'appel d'une fonction (ou pas la fonction est en ligne), il y a 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 déclaration figurant dans corps de la fonction . Il y a aussi un point de séquence après la copie d'un valeur retournée et avant la exécution de toute expression extérieure la fonction .

27
demandé sur Community 2011-01-17 06:17:37
la source

6 ответов

cela dépend de la définition de Sun . Ce qui suit est bien défini

struct A {
  A &Fun(int);
  A &Gun(int);
  A &Sun(int&);
  A &Tun();
};

void g() {
  A someInstance;
  int k = 0;
  someInstance.Fun(++k).Gun(10).Sun(k).Tun();
}

si vous changez le type de paramètre Sun en int , il devient non défini. dessinez un arbre de la version prenant un int .

                     <eval body of Fun>
                             |
                             % // pre-call sequence point
                             | 
 { S(increment, k) }  <-  E(++x) 
                             |     
                      E(Fun(++k).Gun(10))
                             |
                      .------+-----.       .-- V(k)--%--<eval body of Sun>
                     /              \     /
                   E(Fun(++k).Gun(10).Sun(k))
                              |
                    .---------+---------. 
                   /                     \ 
                 E(Fun(++k).Gun(10).Sun(k).Tun())
                              |
                              % // full-expression sequence point

comme on peut le voir, nous avons une lecture de k (désigné par V(k) ) et un effet secondaire sur k (en haut) qui ne sont pas séparé par un point de séquence: dans cette expression, par rapport à chacune des autres sous-expressions, il n'y a aucun point de séquence. Le très bas % signifie le point de séquence de la pleine expression.

12
répondu Johannes Schaub - litb 2017-05-23 14:51:32
la source

je pense que si vous lisez exactement ce que cette citation standard dit, le premier cas ne sera pas bien défini:

lors de l'appel d'une fonction (que la fonction soit ou non en ligne), il y a un point de séquence après l'évaluation de tous les arguments de fonction (s'il y en a) qui a lieu avant l'exécution d'expressions ou d'énoncés dans le corps de fonction

ce que cela nous dit n'est pas que "la seule chose qui peut se produire après que les arguments pour une fonction ont été évalués est l'appel de fonction réel", mais simplement qu'il y a un point de séquence à un moment donné après l'évaluation des arguments finis, et avant l'appel de fonction.

Mais si vous imaginez un cas comme celui-ci:

foo(X).bar(Y)

la seule garantie que cela nous donne est que:

  • X est évalué avant l'appel à foo , et
  • Y est évalué avant l'appel à bar .

mais un tel ordre serait encore possible:

  1. évaluer X
  2. evalute Y
  3. (point de séquence séparant X de foo appel)
  4. appel foo
  5. (séquence point séparant Y de bar appel)
  6. appel bar

et bien sûr, nous pourrions aussi échanger autour des deux premiers éléments, en évaluant Y avant X . Pourquoi pas? La norme exige seulement que les arguments pour une fonction soient pleinement évalués avant le premier énoncé du corps de la fonction, et les séquences ci-dessus satisfont à cette exigence.

C'est mon interprétation, moins. Il ne semble pas dire que rien autrement peut se produire entre l'évaluation de l'argument et le corps de fonction -- juste que ces deux sont séparés par un point de séquence.

22
répondu jalf 2011-01-17 06:51:19
la source

ce comportement n'est pas défini, car la valeur de k est à la fois modifiée et lue dans la même expression, sans point de séquence intermédiaire. Voir l'excellente réponse longue à cette question .

la citation de 1.9.17 vous dit que tous les arguments de fonction sont évalués avant le corps de la fonction est appelée, mais ne dit rien au sujet de l'ordre relatif d'évaluation des arguments à des appels de fonction différents dans le même l'expression -- aucune garantie que "++k Fun() est évalué avant de k dans le Soleil()".

eat(++k);drink(10);sleep(k);

est différent parce que ; est un point de séquence, de sorte que l'ordre d'évaluation est bien défini.

10
répondu David Gelhar 2017-05-23 15:08:03
la source

comme petit test, considérez:

#include <iostream>

struct X
{
    const X& f(int n) const
    {
        std::cout << n << '\n';
        return *this;
    }
};

int main()
{
    int n = 1;

    X x;

    x.f(++n).f(++n).f(++n).f(++n);
}

Je l'exécute avec gcc 3.4.6 et aucune optimisation et obtenir:

5
4
3
2

...avec-O3...

2
3
4
5

donc, soit cette version de 3.4.6 avait un bug majeur (ce qui est un peu difficile à croire), soit la séquence n'est pas définie comme Philip Potter l'a suggéré. (GCC 4.1.1 avec / sans-O3 produit 5, 5, 5, 5.)

EDIT-mon résumé de la discussion dans commentaires ci-dessous:

  • 3.4.6 aurait vraiment pu avoir un bug (Eh bien, oui)
  • beaucoup de nouveaux compilateurs arrivent à produire 5/5/5/5... c'est qu'un comportement défini?
    • probablement pas, car il correspond à tous les effets secondaires incrémentiels étant "actionnés" avant l'un des appels de fonction sont faites, ce qui n'est pas un comportement que quelqu'un ici a suggéré pourrait être garanti par la norme
  • ce n'est pas une très bonne approche pour étudier les exigences de la norme (en particulier avec un compilateur plus ancien comme 3.4.6): d'accord, mais c'est un contrôle de santé utile
8
répondu Tony Delroy 2011-01-17 09:05:10
la source

je sais que le comportement des compilateurs ne peut pas vraiment prouver quoi que ce soit, mais j'ai pensé qu'il serait intéressant de vérifier ce que la représentation interne d'un compilateur donnerait (encore un peu plus haut que l'inspection de l'assemblage).

j'ai utilisé le Clang/LLVM démo en ligne avec ce code:

#include <stdio.h>
#include <stdlib.h>

struct X
{
  X const& f(int i) const
  {
    printf("%d\n", i);
    return *this;
  }
};

int main(int argc, char **argv) {
  int i = 0;
  X x;
  x.f(++i).f(++i).f(++i);         // line 16
}

et compilé avec les optimisations standard (en mode C++), il donne:

/tmp/webcompile / _13371_0.cc: en fonction ' int main (int, char**)':

/tmp/webcompile / _13371_0.cc: 16: avertissement: l'opération "i" peut ne pas être définie

que j'ai trouvé intéressant (est-ce qu'un autre compilateur a prévenu à ce sujet ? Comeau en ligne n'a pas)


comme un côté, il a également produit la représentation intermédiaire suivante (défiler à droite):

@.str = private constant [4 x i8] c"%d"151910920"A"151910920"", align 1 ; <[4 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  %0 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  %1 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  %2 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  ret i32 0
}

apparemment, Clang se comporte comme gcc 4.x.x fait et évalue d'abord tous les arguments avant d'effectuer n'importe quel appel de fonction.

1
répondu Matthieu M. 2011-01-17 11:04:55
la source

le second cas est certainement bien défini. Une chaîne de jetons qui se termine par un point-virgule est une instruction atomique en C++. Chaque relevé est analysé, traité et complété avant que le prochain relevé ne commence.

0
répondu ThomasMcLeod 2011-01-17 06:28:30
la source