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.
Avis de non-responsabilité : Ok. Cette réponse est un peu longue. Soyez donc patient en la lisant. Si vous connaissez déjà ces choses, les relire ne vous rendra pas fou.
Pré-requis : Une connaissance élémentaire de Norme C++
Que sont les points de séquence ?
La norme dit
À certains points précis de la séquence d'exécution appelés points de séquence tous effets secondaires des évaluations précédentes doivent être complètes et aucun effets secondaires des évaluations ultérieures doivent avoir eu lieu. (§1.9/7)
Effets secondaires ? Que 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, 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 modifié en raison de l'effet secondaire de ++
opérateur.
Jusqu'à présent, 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 où tous les effets secondaires observés jusqu'à présent sont garantis.
Quels sont les points de séquence communs énuméré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 ; en func(a,a++)
,
n'est pas un opérateur de virgule, c'est simplement un séparateur entre les arguments a
y a++
. Ainsi, le comportement est indé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 la fonction (le cas échéant) qui 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 font pas partie lexicalement de l'expression complète. pas lexicalement partie de l'expression complète. Par exemple, les sous-expressions impliquées dans l'évaluation des expressions d'arguments par défaut (8.3.6) sont considérées comme étant créées dans l'expression qui appelle la fonction, et non dans 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 la clause 5. Lorsque l'un de ces opérateurs est surchargé (clause 13) dans un contexte valide, désignant ainsi une fonction 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
le comportement, tel qu'il pourrait résulter de l'utilisation d'une construction de programme erronée ou de données erronées, pour lequel la présente Norme Internationale impose aucune exigence 3 .
On peut également s'attendre à un comportement non défini lorsque cette Norme internationale omet la description de toute définition explicite du comportement.
3 : le comportement indéfini autorisé va de l'ignorance totale 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 et caractéristique de l'environnement (avec ou sans émission de code). avec ou sans l'émission d'un message de diagnostic), à l'interruption d'une traduction ou d'une exécution (avec l'émission d'un message de diagnostic).
En bref, un comportement indéfini signifie tout ce qui est peuvent arriver, des démons qui sortent de ton nez à ta copine qui tombe enceinte.
Quelle est la relation entre le comportement indéfini et les points de séquence ?
Avant d'entrer dans le vif du sujet, vous devez connaître la ou les différences entre Comportement indé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 aquí .
Maintenant, la norme en §5/4
dit
- 1) Entre le point de séquence précédent et le suivant, la valeur stockée d'un objet scalaire doit être modifiée au maximum une fois par l'évaluation d'une expression.
Qu'est-ce que cela signifie ?
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
se trouve généralement à la fin du point-virgule, et l'élément previous sequence point
est à la fin de la déclaration précédente. Une expression peut également contenir des éléments intermédiaires sequence points
.
D'après 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 correctes :
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, on ne peut accéder à la valeur antérieure que pour déterminer la valeur à stocker.
Qu'est-ce que cela signifie ? Cela signifie que si un objet est écrit à l'intérieur d'une expression complète, tous les accès à cet objet à 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. C'est donc bien.
Cette règle limite effectivement les expressions légales à 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
n'est pas autorisé parce que l'un des accès de i
(celui de a[i]
) n'a rien à voir avec la valeur qui finit par être stockée dans i (ce qui se produit plus dans i++
), et il n'y a donc pas de bon moyen 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. Le comportement est donc indéfini.
Exemple 3 :
int x = i + i++ ;// Similar to above
Réponse complémentaire pour C++11 aquí .