web-dev-qa-db-fra.com

Comportement indéfini et points de séquence

Que sont les "points de séquence"?

Quelle est la relation entre le comportement indéfini et les points de séquence?

J'utilise souvent des expressions drôles et compliqué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 consulter la question suivante Comportement non défini et points de séquence rechargés.

(Remarque: il s’agit d’une entrée dans C++ FAQ de Stack Overflow. Si vous souhaitez critiquer l’idée de fournir un FAQ sous cette forme, alors la publication sur meta qui a commencé tout cela serait l'endroit pour le faire. Les réponses à cette question sont suivies dans le C++ chatroom , où l'idée FAQ a commencé à la base, donc votre réponse sera très probablement lue par ceux qui en ont eu l'idée.)

931
Prasoon Saurav

C++ 98 et C++ 03

Cette réponse concerne 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", "non séquencées" ou "séquencées de manière indéterminée". L'effet net est essentiellement le même, mais la terminologie est différente.


Disclaimer : D'accord. Cette réponse est un peu longue. Alors soyez patient en le lisant. Si vous connaissez déjà ces choses, les relire ne vous rendra pas fou.

Prérequis : Connaissance élémentaire de Norme C++


Que sont les points de séquence?

La norme dit

À certains points spécifiés de la séquence d'exécution appelés points de séquence , tous les effets secondaires des évaluations précédentes doivent être complets et non - effets secondaires des évaluations ultérieures doivent avoir eu lieu. (§1.9/7)

Effets secondaires? Quels sont les effets secondaires?

L'évaluation d'une expression produit quelque chose et si, en plus, l'état de l'environnement d'exécution change, on dit que l'expression (son évaluation) a des 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 modifiée en raison de l'effet secondaire de l'opérateur _++_.

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

Le point de séquence est un point dans le temps où la poussière est retombée et où tous les effets secondaires observés jusqu’à présent sont garantis complets.


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

Ce sont:

  • à la fin de l'évaluation de l'expression complète (_§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; dans func(a,a++) _,_ n'est pas un opérateur de virgule, c'est simplement un séparateur entre les arguments a et _a++_. Ainsi, le comportement est indéfini case (si a est considéré comme un type primitif))
  • lors d'un appel de fonction (que la fonction soit ou non en ligne), après l'évaluation de tous les arguments de la fonction (le cas échéant) qui a lieu avant l'exécution d'expressions ou d'instructions éventuelles dans le corps de la fonction (_§1.9/17_).

1: Remarque: l'évaluation d'une expression complète peut inclure l'évaluation de sous-expressions qui ne font pas partie lexicalement de l'expression complète. 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, pas l'expression qui définit l'argument par défaut.

2: les opérateurs indiqués sont les opérateurs intégrés, tels que décrits dans l'article 5. Lorsque l'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éfini?

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

comportement, tel qu'il pourrait survenir lors de l'utilisation d'une construction de programme erronée ou de données erronées, pour lequel la présente Norme internationale n'impose aucune exigence 3.

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

3: le comportement non défini autorisé va d'ignorer complètement la situation avec des résultats imprévisibles, de se comporter pendant la traduction ou l'exécution du programme de manière documentée, caractéristique de l'environnement (avec ou sans émission d'un message de diagnostic), jusqu'à la fin de la traduction ou de l'exécution (avec l'émission d'un message de diagnostic).

En bref, un comportement indéfini signifie que tout peut arriver entre démons qui volent hors de votre nez et votre petite amie qui tombe enceinte.


Quelle est la relation entre le comportement non défini et les points de séquence?

Avant de commencer, vous devez connaître la (les) différence (s) comportement non défini, comportement non spécifié et comportement défini par l'implémentation .

Vous devez également 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 les points de séquence précédent et suivant, la valeur stockée d'un objet scalaire doit être modifiée au plus une fois par l'évaluation d'une expression.

Qu'est-ce que ça veut dire?

De manière informelle, cela signifie qu'entre deux points de séquence, une variable ne doit pas être modifiée plus d'une fois. Dans une instruction d'expression, le _next sequence point_ est généralement au point-virgule de fin et le _previous sequence point_ est à la fin de l'instruction précédente. Une expression peut également contenir un intermédiaire _sequence points_.

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

_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) De plus, l'accès à la valeur antérieure doit être effectué uniquement pour déterminer la valeur à enregistrer.

Qu'est-ce que ça veut dire? Cela signifie que si un objet est écrit dans une expression complète, tout accès à celui-ci dans la même expression doit être directement associé au calcul de la valeur à écrire .

Par exemple, dans _i = i + 1_, tous les accès de i (en LHS et en RHS) sont directement impliqués dans le calcul de la valeur être écrit. Donc ça va.

Cette règle limite effectivement les expressions juridiques à celles dans lesquelles les accès précèdent manifestement 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 interdit car l'un des accès de i (celui dans _a[i]_) n'a rien à voir avec la valeur qui finit par être stockée dans i (ce qui se passe dans _i++_), et il n'y a donc aucun moyen de définir - 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 pour C++ 11 ici .

668
Prasoon Saurav

Ceci fait suite à mon réponse précédente et contient des informations relatives à C++ 11..


Prérequis: 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?

Yes! C'est très vrai.

Les points de séquence ont été remplacés par Séquencé avant ​​et Séquencé après (et Sans séquence et Séquence indéterminée) relations en C++ 11.


Quelle est exactement cette chose "séquence avant"?

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

entre les évaluations exécutées par un seul thread et induit un ordre partiel strict1

Formellement, cela signifie deux évaluations quelconques(Voir ci-dessous) A et B, si A est séquence avant ​​B, l'exécution de A doit précéder l'exécution de B. Si A n'est pas séquencé avant B et si B n'est pas séquencé avant A, alors A et B sont sans séquence 2.

Les évaluations A et B sont séquentielles indéterminées lorsque A est séquencé avant que B ou B soit séquencé avant A, mais elle n'est pas spécifiée.3.

[REMARQUES]
1: Un ordre partiel strict est un --- relation binaire"<" sur un ensemble P qui est asymmetric , et transitive , c’est-à-dire, pour tout a, b et c dans P, nous avons ceci:
........(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 peut se chevaucher .
3: Les évaluations séquentielles indéterminées ne peut pas se chevaucher , mais l'une ou l'autre peut être exécutée en premier.


Quel est le sens 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:

Maintenant (§1.9/14) dit:

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

  • Exemple trivial:

    int x;x = 10;++x;

    Le calcul de la valeur et les effets secondaires associés à ++x sont séquencés après le calcul de la valeur et les effets secondaires de x = 10;.


Donc, il doit y avoir une relation entre le comportement indéfini et les choses mentionnées ci-dessus, non?

Oui! Bien.

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

Sauf indication contraire, les évaluations d'opérandes d'opérateurs individuels et de sous-expressions d'expressions individuelles sont sans séquence4.

Par exemple :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. L'évaluation des opérandes de l'opérateur + n'est pas séquencée les uns par rapport aux autres.
  2. L'évaluation des opérandes des opérateurs << et >> n'est pas séquencée l'un par rapport à l'autre.

4: Dans une expression évaluée plusieurs fois au cours de l'exécution d'un programme, les évaluations sans séquence et séquence indéterminée de ses sous-expressions ne doivent pas nécessairement être effectuées de manière cohérente dans des évaluations différentes.

(§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 que dans x + y, les calculs de valeur de x et y sont séquencés avant le calcul de valeur de (x + y).

Plus important

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

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

ou

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

le comportement est ndefined.

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 (qu’elle soit ou non en ligne), chaque calcul de valeur et effet secondaire associé à une expression d’argument, ou à l’expression postfixe désignant la fonction appelée, est séquencé avant l’exécution de chaque expression ou instruction du corps de la appelée fonction. [ Remarque: Les calculs de valeur et les effets secondaires associés à des expressions d'argument différentes ne sont pas séquencés. - note finale ]

Les expressions (5), (7) et (8) n'appellent pas de comportement indéfini. Découvrez les réponses suivantes pour une explication plus détaillée.


Note finale:

Si vous trouvez une faille dans le post, laissez un commentaire. Utilisateurs expérimentés (avec une représentation> 20000), n'hésitez pas à modifier l'article afin de corriger les fautes de frappe et autres erreurs.

273
Prasoon Saurav

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

f (a,b)

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

11
Yttrill

Dans C99(ISO/IEC 9899:TC3) qui semble absent de cette discussion à ce jour, les steteents suivants sont créés concernant l’ordre d’évaluation.

[...] l'ordre d'évaluation des sous-expressions et l'ordre dans lequel les effets secondaires sont indéterminés. (Section 6.5, p. 67)

L'ordre d'évaluation des opérandes est indéterminé. Si une tentative est fait pour modifier le résultat d'un opérateur d'assignation ou pour y accéder après le point de séquence suivant, le comportement n'est pas défini (section 6.5.16, page 91).

0
awiebe