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» ou «non séquencées» ou «séquencées de façon indéterminée» à la place. L'effet net est essentiellement le même, mais la terminologie est différente.
Avertissement : 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 : Une connaissance élémentaire du standard 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 aucun effet secondaire des évaluations suivantes ne doit s'être produit. (§1.9 / 7)
Effets secondaires? Quels sont les effets secondaires?
L'évaluation d'une expression produit quelque chose et si en plus il y a un changement dans l'état de l'environnement d'exécution, il est 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 modifiée en raison de l'effet secondaire de l' ++
opérateur.
Jusqu'ici tout va bien. Passons aux points de séquence. Une définition d'alternance de points seq donnée par l'auteur comp.lang.c Steve Summit
:
Le point de séquence est un moment où la poussière s'est déposée et tous les effets secondaires qui ont été vus jusqu'à présent sont garantis d'être complets.
Quels sont les points de séquence courants 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 virgule; in func(a,a++)
,
n'est pas un opérateur virgule, c'est simplement un séparateur entre les arguments a
et 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) qui a lieu avant l'exécution de toute expression ou instruction dans le corps de fonction ( §1.9/17
).
1: Remarque: l'évaluation d'une expression complète peut inclure l'évaluation de sous-expressions qui ne font pas lexiquement partie 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 étant 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, comme décrit 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 indé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 de comportement.
3: un comportement indéfini autorisé va de l'ignorance complète de la situation avec des résultats imprévisibles, au comportement pendant la traduction ou l'exécution du programme d'une manière documentée caractéristique de l'environnement (avec ou sans l'émission d'un message de diagnostic), jusqu'à la fin d'une traduction ou d'une exécution (avec émission d'un message de diagnostic).
En bref, un comportement indéfini signifie que tout peut arriver, des démons sortant du nez à la grossesse de votre petite amie.
Quelle est la relation entre le comportement indéfini et les points de séquence?
Avant d'entrer dans les détails, vous devez connaître la ou les différences entre le comportement non défini, le comportement non spécifié et le comportement défini par l'implémentation .
Vous devez également le savoir 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 en §5/4
dit
- 1) Entre le point de séquence précédent et suivant, un objet scalaire doit voir 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 instruction d'expression, l' next sequence point
est généralement au point-virgule de fin et l' previous sequence point
est à la fin de l'instruction précédente. Une expression peut également contenir un intermédiaire sequence points
.
Dans 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 conviennent:
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 antérieure ne doit être consultée que pour déterminer la valeur à stocker.
Qu'est-ce que ça veut dire? Cela signifie que si un objet est écrit dans une expression complète, tous les accès à celui-ci dans la même expression doivent être directement impliqués dans le 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 à écrire. Donc ça va.
Cette règle contraint 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 en a[i]
) n'a rien à voir avec la valeur qui finit par être stockée dans i (ce qui se produit en i++
), et donc il n'y a pas de bon moyen de définir - que ce soit pour notre compréhension ou du compilateur - si l'accès doit avoir lieu avant ou après le stockage de la valeur incrémentée. Le comportement n'est donc pas défini.
Exemple 3:
int x = i + i++ ;// Similar to above
Suivez la réponse pour C ++ 11 ici .
*p++ = 4
n'est pas un comportement indéfini.*p++
est interprété comme*(p++)
.p++
renvoiep
(une copie) et la valeur stockée à l'adresse précédente. Pourquoi cela invoquerait-il UB? C'est parfaitement bien.