Comportement défini pour les expressions

La norme C99 dit dans $ 6.5.2.

Entre le sharepoint séquence précédent et le suivant, la valeur stockée d’un object doit être modifiée au plus une fois par l’évaluation d’une expression. De plus, la valeur antérieure doit être lue uniquement pour déterminer la valeur à stocker .

(souligné par moi)

Il est ensuite noté que l’exemple suivant est valide (ce qui semble évident au premier abord)

a[i] = i; 

Bien que cela n’énonce pas explicitement ce que sont a et i .

Bien que je pense que non, j’aimerais savoir si cet exemple couvre le cas suivant:

 int i = 0, *a = &i; a[i] = i; 

Cela ne changera pas la valeur de i , mais accédera à la valeur de i pour déterminer l’adresse où placer la valeur. Ou est-il indifférent d’atsortingbuer à i une valeur déjà stockée dans i ? S’il vous plaît jeter un peu de lumière.


Question bonus; Qu’en est-il d’ a[i]++ ou d’ a[i] = 1 ?

La première phrase:

Entre le sharepoint séquence précédent et le suivant, la valeur stockée d’un object doit être modifiée au plus une fois par l’évaluation d’une expression.

est assez clair. Le langage n’impose pas d’ordre d’évaluation aux sous-expressions sauf s’il existe un point d’ordre entre elles. Plutôt que d’exiger un ordre d’évaluation non spécifié, il indique que modifier un object deux fois produit un comportement indéfini. Cela permet une optimisation agressive tout en permettant d’écrire du code qui respecte les règles.

La phrase suivante:

En outre, la valeur antérieure doit être lue uniquement pour déterminer la valeur à stocker

ne semble pas intuitif au premier (et au deuxième) regard; pourquoi l’objective pour lequel une valeur est lue peut-il avoir une incidence sur le comportement défini d’une expression?

Mais cela reflète le fait que si une sous-expression B dépend du résultat d’une sous-expression A, alors A doit être évalué avant que B ne puisse être évalué. Les normes C90 et C99 ne l’indiquent pas explicitement.

Une violation plus claire de cette phrase, donnée dans un exemple dans la note de bas de page, est la suivante:

 a[i++] = i; /* undefined behavior */ 

En supposant que a soit un object de tableau déclaré et que i est un object entier déclaré (pas de pointeur ni de ruse de macro), aucun object n’est modifié plus d’une fois, de sorte qu’il ne viole pas la première phrase. Mais l’évaluation de i++ sur le LHS détermine quel object doit être modifié et l’évaluation de i sur le RHS détermine la valeur à stocker dans cet object – ainsi que l’ordre relatif de l’opération de lecture sur le RHS et de l’opération d’écriture. sur le LHS n’est pas défini. Là encore, le langage aurait pu exiger que les sous-expressions soient évaluées dans un ordre non spécifié, mais au lieu de cela, il a laissé le comportement entier non défini pour permettre une optimisation plus agressive.

Dans votre exemple:

 int i = 0, *a = &i; a[i] = i; /* undefined behavior (I think) */ 

la valeur précédente de i est lue à la fois pour déterminer la valeur à stocker et pour déterminer l’object dans lequel il va être stocké. Puisqu’un a[i] réfère à i (mais uniquement parce que i==0 ), en modifiant la valeur de i changerait l’object auquel fait référence la lvalue a[i] . Il arrive dans ce cas que la valeur stockée dans i soit la même que la valeur qui y était déjà stockée ( 0 ), mais la norme ne crée pas d’exception pour les magasins qui arrivent à stocker la même valeur. Je crois que le comportement n’est pas défini. (Bien entendu, l’exemple de la norme ne visait pas à couvrir ce cas; il suppose implicitement que a est un object de tableau déclaré non lié à i .)

En ce qui concerne l’exemple indiqué dans la norme, il est autorisé:

 int a[10], i = 0; /* implicit, not stated in standard */ a[i] = i; 

on pourrait interpréter la norme en disant qu’elle n’est pas définie. Mais je pense que la deuxième phrase, faisant référence à “la valeur antérieure”, s’applique uniquement à la valeur d’un object modifié par l’expression. i n’est jamais modifié par l’expression, il n’y a donc pas de conflit. La valeur de i est utilisée à la fois pour déterminer l’object à modifier par l’affectation et la valeur à y stocker, mais c’est correct, car la valeur de i elle-même ne change jamais. La valeur de i n’est pas “la valeur antérieure”, c’est juste la valeur.

La norme C11 a un nouveau modèle pour ce type d’évaluation de l’expression – ou plutôt, elle exprime le même modèle dans des termes différents. Plutôt que des “points de séquence”, il est question d’effets secondaires séquencés les uns avant les autres, ou l’un après l’autre, ou l’un après l’autre. Cela rend explicite l’idée que si une sous-expression B dépend du résultat d’une sous-expression A, alors A doit être évalué avant que B puisse être évalué.

Dans le brouillon N1570 , la section 6.5 dit:

1 Une expression est une séquence d’opérateurs et d’opérandes qui spécifie le calcul d’une valeur, ou qui désigne un object ou une fonction, ou qui génère des effets secondaires ou qui en effectue une combinaison. 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.

2 Si un effet secondaire sur un object scalaire n’est pas séquencé par rapport à un effet secondaire différent sur le même object scalaire ou à un calcul de valeur utilisant la valeur du même object scalaire, le comportement est indéfini. S’il existe plusieurs classements autorisés des sous-expressions d’une expression, le comportement n’est pas défini si un tel effet secondaire non séquencé se produit dans l’un des classements.

3 Le groupement d’opérateurs et d’opérandes est indiqué par la syntaxe. Sauf indication contraire ultérieure, les effets secondaires et les calculs de valeur des sous-expressions ne sont pas séquencés.

La lecture de la valeur d’un object pour déterminer où l’ enregistrer ne compte pas pour “déterminer la valeur à enregistrer” . Cela signifie que le seul sharepoint discorde peut être de savoir si nous “modifions” ou non l’object i : si nous le sums, il n’est pas défini; si nous ne le sums pas, c’est bon.

Stocker la valeur 0 dans un object contenant déjà la valeur 0 compte-t-il comme “modification de la valeur stockée”? Selon la définition simplement anglaise de «modifier», il me faudrait dire non; laisser quelque chose inchangé est le contraire de le modifier.

Il est toutefois clair qu’il s’agirait d’un comportement non défini:

 int i = 0, *a = &i; a[i] = 1; 

Ici, il ne fait aucun doute que la valeur stockée est lue dans un but autre que la détermination de la valeur à stocker (la valeur à stocker est une constante) et que la valeur de i est modifiée.