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


Quels sont les "points de séquence"?

Quelle est la relation entre un comportement indéfini et des points de séquence?

J'utilise souvent des expressions drôles et alambiquées comme a[++i] = i;, pour me sentir mieux. Pourquoi devrais-je arrêter de les utiliser?

Si vous avez lu ceci, assurez-vous de visiter la question de suivi Comportement indéfini et points de séquence rechargés.

(Note: Ceci est censé être une entrée pour FAQ C ++ de Stack Overflow. Si vous voulez critiquer l'idée de fournir une FAQ dans ce formulaire, alors l'affichage sur meta qui a commencé tout cela serait l'endroit pour le faire. Les réponses à cette question sont suivies dans le Chatroom C ++, où l'idée de FAQ a commencé en premier lieu, ainsi votre réponse est très susceptible d'être lue par ceux qui ont eu l'idée.)


897


origine


Réponses:


C ++ 98 et C ++ 03

Cette réponse est pour les anciennes versions de la norme C ++. Les versions C ++ 11 et C ++ 14 de la norme ne contiennent pas formellement de «points de séquence»; les opérations sont «séquencées avant» ou «non séquencées» ou «séquentiellement indéterminée». L'effet net est essentiellement le même, mais la terminologie est différente.


Avertissement : D'accord. Cette réponse est un peu longue. Ayez donc de la patience en le lisant. Si vous connaissez déjà ces choses, les relire ne vous rendra pas fou.

Conditions préalables : Une connaissance élémentaire de Norme C ++ 


Quels sont les points de séquence?

La norme dit

À certains points spécifiés dans la séquence d'exécution appelée points de séquence, tout Effets secondaires des évaluations précédentes   doit être complet et non Effets secondaires d'évaluations ultérieures aura eu lieu. (§1.9 / 7)

Effets secondaires? Quels sont les effets secondaires?

L'évaluation d'une expression produit quelque chose et si de plus il y a un changement dans l'état de l'environnement d'exécution, on dit que l'expression (son évaluation) a un ou plusieurs effets secondaires.

Par exemple:

int x = y++; //where y is also an int

En plus de l'opération d'initialisation, la valeur de y est changé en raison de l'effet secondaire de ++ opérateur.

Jusqu'ici tout va bien. Passons aux points de séquence. Une définition d'alternance de seq-points donnée par l'auteur comp.lang.c Steve Summit:

Le point de séquence est un moment où la poussière est retombée et tous les effets secondaires observés jusqu'ici sont garantis.


Quels sont les points de séquence communs répertoriés dans la norme C ++?

Ce sont:

  • à la fin de l'évaluation de la pleine expression (§1.9/16) (Une expression complète est une expression qui n'est pas une sous-expression d'une autre expression.)1

Exemple :

int a = 5; // ; is a sequence point here
  • dans l'évaluation de chacune des expressions suivantes après l'évaluation de la première expression (§1.9/18) 2

    • a && b (§5.14) 
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18) (ici a, b est un opérateur de virgule; func(a,a++)  , n'est pas un opérateur de virgule, c'est simplement un séparateur entre les arguments aet a++. Ainsi, le comportement n'est pas défini dans ce cas (si a est considéré comme un type primitif))
  • lors d'un appel de fonction (que la fonction soit en ligne ou non), après l'évaluation de tous les arguments de fonction (le cas échéant) a lieu avant l'exécution de toute expression ou instruction dans le corps de la fonction (§1.9/17).

1: Note: l'évaluation d'une expression complète peut inclure l'évaluation de sous-expressions qui ne sont pas lexicalement partie de la pleine expression. Par exemple, les sous-expressions impliquées dans l'évaluation des expressions d'argument par défaut (8.3.6) sont considérées comme créées dans l'expression qui appelle la fonction, et non l'expression qui définit l'argument par défaut.

2: Les opérateurs indiqués sont les opérateurs intégrés, comme décrit à l'article 5. Lorsqu'un de ces opérateurs est surchargé (article 13) dans un contexte valide, désignant ainsi une fonction d'opérateur définie par l'utilisateur, l'expression désigne une invocation de fonction et les opérandes forment une liste d'arguments, sans point de séquence implicite entre eux.


Qu'est-ce qu'un comportement indéterminé?

La norme définit le comportement indéfini dans la section §1.3.12 comme

comportement, tel qu'il pourrait résulter de l'utilisation d'une construction de programme erronée ou de données erronées, pour laquelle la présente Norme internationale impose pas d'exigences 3.

Un comportement indéfini peut également être attendu lorsque   Norme internationale omet la description de toute définition explicite du comportement.

 3: le comportement indéfini admissible va de l'ignorance complète de la situation avec des résultats imprévisibles à un comportement de traduction ou d'exécution du programme d'une manière documentée et caractéristique de l'environnement (avec ou sans sortie d'un message de diagnostic), pour terminer une traduction ou une exécution (avec émission d'un message de diagnostic).

En résumé, un comportement non défini signifie n'importe quoi Il peut arriver que des démons sortent de votre nez pour que votre petite amie tombe enceinte.


Quelle est la relation entre un comportement indéterminé et des points de séquence?

Avant d'entrer dans ce que vous devez savoir la différence (s) entre Comportement indéfini, comportement non spécifié et comportement défini par l'implémentation.

Vous devez aussi savoir que the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Par exemple:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Un autre exemple ici.


Maintenant, la norme dans §5/4 dit

  • 1) Entre le point de séquence précédent et suivant, un objet scalaire doit avoir sa valeur stockée modifiée au plus une fois par l'évaluation d'une expression. 

Qu'est-ce que ça veut dire?

Informellement, cela signifie qu'entre deux points de séquence, une variable ne doit pas être modifiée plus d'une fois. Dans une déclaration d'expression, le next sequence point est généralement au point-virgule de terminaison, et le previous sequence point est à la fin de la déclaration précédente. Une expression peut également contenir des intermédiaires sequence points.

À partir de la phrase ci-dessus, les expressions suivantes invoquent un comportement indéterminé:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Mais les expressions suivantes sont bien:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) En outre, la valeur précédente doit être accessible uniquement pour déterminer la valeur à stocker.

Qu'est-ce que ça veut dire? Cela signifie que si un objet est écrit à l'intérieur d'une expression complète, tout et tous les accède à l'intérieur de la même expression doit être directement impliqué dans le calcul de la valeur à écrire.

Par exemple dans i = i + 1 tout l'accès de i (en L.H.S et en R.H.S) sont directement impliqué dans le calcul de la valeur à écrire. Donc c'est bien.

Cette règle contraint effectivement les expressions juridiques à celles dans lesquelles les accès précèdent la modification.

Exemple 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Exemple 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

est refusé parce que l'un des accès de i (celui en a[i]) n'a rien à voir avec la valeur qui finit par être stockée dans i (ce qui arrive dans i++), et il n'y a donc pas de bonne façon de définir - que ce soit pour notre compréhension ou celle du compilateur - si l'accès doit avoir lieu avant ou après le stockage de la valeur incrémentée. Donc, le comportement est indéfini.

Exemple 3:

int x = i + i++ ;// Similar to above

Réponse de suivi ici. 


623



Ceci est un suivi de mon réponse précédente et contient du matériel lié à C ++ 11..


Conditions préalables : Une connaissance élémentaire des Relations (Mathématiques).


Est-il vrai qu'il n'y a pas de points de séquence dans C ++ 11?

Oui! C'est très vrai.

Points de séquence ont été remplacés par Séquencé avantet Séquencé après (et Non séquencéet Séquencé pour une période indéterminée) rapports en C ++ 11.


Qu'est-ce que c'est exactement cette chose 'Sequenced before'?

Séquencé avant(§1.9 / 13) est une relation qui est:

entre les évaluations exécutées par un seul fil et induit un commande partielle stricte1

Formellement cela signifie donné deux évaluations(Voir ci-dessous)  Aet B, si A est séquencé avant  B, puis l'exécution de A  doit précéder l'exécution de B. Si A n'est pas séquencé avant Bet B n'est pas séquencé avant A, puis Aet B sont non séquencé  2.

Évaluations Aet B sont séquentiellement indéterminé quand soit A est séquencé avant B ou B est séquencé avant A, mais il est indéterminé3.

[REMARQUES]
  1: Une commande partielle stricte est un relation binaire  "<" sur un ensemble P lequel est asymmetric, et transitive, c'est-à-dire pour tous a, b, et c dans P, nous avons cela:
 
  ........(je). si a <b alors ¬ (b <a) (asymmetry)
  ........ (ii). si a <b et b <c alors a <c (transitivity).
  2: L'exécution de évaluations non séquencées pouvez chevauchement.
  3: Évaluations séquentiellement indéterminées ne peux pas chevauchement, mais soit pourrait être exécuté en premier.


 Quelle est la signification du mot 'évaluation' dans le contexte de C ++ 11?

En C ++ 11, l'évaluation d'une expression (ou d'une sous-expression) comprend généralement:

  • calculs de valeur (y compris la détermination de l'identité d'un objet pour évaluation de glvalue et récupérer une valeur précédemment affectée à un objet pour évaluation prvalue) et

  • initiation de Effets secondaires.

Maintenant (§1.9 / 14) dit:

Chaque calcul de valeur et effet secondaire associé à une expression complète est séquencé avant chaque calcul de valeur et effet secondaire associé à la prochaine expression complète à évaluer.

  • Exemple trivial:

    int x; x = 10; ++x;

    Calcul de la valeur et effet secondaire associé à ++x est séquencé après le calcul de la valeur et l'effet secondaire de x = 10; 


Donc, il doit y avoir une certaine relation entre Undefined Behavior et les choses mentionnées ci-dessus, non?

Oui! Droite.

Dans (§1.9 / 15), il a été mentionné que

Sauf indication contraire, les évaluations des opérandes des opérateurs individuels et des sous-expressions des expressions individuelles sont non séquencé4.

Par exemple :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Évaluation des opérandes de + l'opérateur ne sont pas séparés l'un par rapport à l'autre.
  2. Évaluation des opérandes de <<et >> les opérateurs ne sont pas séparés l'un par rapport à l'autre.

 4: Dans une expression qui est évaluée plus d'une fois pendant l'exécution d'un programme, non séquencéet séquentiellement indéterminé les évaluations de ses sous-expressions n'ont pas besoin d'être effectuées de manière cohérente dans différentes évaluations.

(§1.9 / 15)   Les calculs de valeur des opérandes d'un   opérateur sont séquencés avant le calcul de la valeur du résultat de l'opérateur.

Cela signifie en x + y la valeur de calcul de xet y sont séquencés avant le calcul de la valeur de (x + y).

Plus important

(§1.9 / 15) Si un effet secondaire sur un objet scalaire est non séquencé par rapport à

(une) un autre effet secondaire sur le même objet scalaire 

ou

(b) un calcul de valeur utilisant la valeur du même objet scalaire.

le comportement est indéfini.

Exemples:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour 
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

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 l'exécution de chaque expression ou instruction dans le corps du appelée fonction. [Remarque:  Les calculs de valeur et les effets secondaires associés aux différentes expressions d'argument ne sont pas séquencés. - note de fin]

Expressions (5), (7)et (8) n'invoque pas de comportement indéfini. Consultez les réponses suivantes pour une explication plus détaillée.


Note finale :

Si vous trouvez un défaut dans le message s'il vous plaît laissez un commentaire. Power-users (Avec rep> 20000), n'hésitez pas à modifier le post pour corriger les fautes de frappe et autres erreurs.


257



C ++ 17 (N4659) inclut une proposition Affiner l'ordre d'évaluation des expressions pour C ++ idiomatique qui définit un ordre plus strict d'évaluation de l'expression.

En particulier, phrase suivante était ajouté:

8.18 Opérateurs d'assignation et d'assignation composée:
....

Dans tous les cas, l'affectation est séquencée après la valeur   calcul des opérandes droit et gauche, et avant le calcul de la valeur de l'expression d'affectation.    L'opérande de droite est séquencé avant l'opérande de gauche.

Il rend plusieurs cas de comportement indéfini, y compris celui en question:

a[++i] = i;

Cependant, plusieurs autres cas similaires conduisent encore à un comportement indéfini.

Dans N4140:

i = i++ + 1; // the behavior is undefined

Mais en N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Bien sûr, l'utilisation d'un compilateur compatible C ++ 17 ne signifie pas nécessairement que l'on devrait commencer à écrire de telles expressions.


13



Je suppose qu'il y a une raison fondamentale au changement, il n'est pas simplement superficiel de clarifier l'ancienne interprétation: cette raison est la concurrence. L'ordre d'élaboration non spécifié est simplement la sélection de plusieurs ordres possibles en série, ce qui est assez différent des ordres avant et après, parce que s'il n'y a pas d'ordre spécifié, l'évaluation simultanée est possible: pas avec les anciennes règles. Par exemple dans:

f (a,b)

auparavant soit a puis b, soit, b alors a. Maintenant, a et b peuvent être évalués avec des instructions entrelacées ou même sur des cœurs différents.


11