2009-08-03 2 views
6

Dire que j'ai une expression de ce typeQuand exactement l'opérateur d'incrément de postfix est-il évalué dans une expression complexe?

short v = ((p[ i++ ] & 0xFF) << 4 | (p[ i ] & 0xF0000000) >> 28; 

avec p étant un pointeur vers un tableau alloué dynamiquement des nombres entiers 32 bits.

Lorsque exactement sera i être incrémenté? J'ai remarqué que le code ci-dessus fournit une valeur différente pour v que le code suivant:

short v = (p[ i++ ] & 0xFF) << 4; 
v |= (p[ i ] & 0xF0000000) >> 28; 

Ma meilleure estimation de ce comportement est que i n'est pas incrémentée avant que le côté droit de l'| ci-dessus est évaluée.

Un aperçu serait apprécié!

Merci à l'avance,

\ Bjoern

Répondre

13

Le problème est l'ordre d'évaluation:
La norme C++ ne définit pas l'ordre d'évaluation des sous-expressions. Ceci est fait pour que le compilateur puisse être aussi agressif que possible dans les optimisations.

Lets décomposer:

  a1      a2 
v = ((p[ i++ ] & 0xFF) << 4 | (p[ i ] & 0xF0000000) >> 28; 

----- 
(1) a1 = p[i] 
(2) i = i + 1 (i++)  after (1)  
(3) a2 = p[i] 
(4) t3 = a1 & 0xFF   after (1) 
(5) t4 = a2 & 0xF0000000 after (3) 
(6) t5 = t3 << 4   after (4) 
(7) t6 = t4 >> 28   after (5) 
(8) t7 = t5 | t6   after (6) and (7) 
(9) v = t7    after (8) 

Maintenant, le compilateur est libre de réarranger expressions ainsi sous tant que ci-dessus « après » clauses ne sont pas violés.Donc, une optimisation facile rapide est déplacer 3 vers le haut d'un emplacement et ensuite supprimer l'expression commune (1) et (3) (maintenant à côté de l'autre) sont les mêmes et ainsi nous pouvons éliminer (3)

Mais le compilateur ne le fait pas avoir à faire l'optimisation (et est probablement meilleur que moi et a d'autres trucs dans sa manche). Mais vous pouvez voir comment la valeur de (a1) sera toujours ce que vous attendez, mais la valeur de (a2) dépendra de l'ordre dans lequel le compilateur décide de faire les autres sous-expressions. La seule garantie que vous avez que le compilateur ne peut pas déplacer les sous-expressions au-delà d'un point de séquence. Votre point de séquence le plus courant est ';' (la fin de la déclaration). Il y en a d'autres, mais j'éviterais d'utiliser cette connaissance car la plupart des gens ne connaissent pas bien le fonctionnement du compilateur. Si vous écrivez du code qui utilise des astuces de point de séquence, quelqu'un peut le factoriser de nouveau pour le rendre plus lisible et maintenant votre astuce vient de se transformer en un be-comportement indéfini.

short v = (p[ i++ ] & 0xFF) << 4; 
v |= (p[ i ] & 0xF0000000) >> 28; 

----- 
(1) a1 = p[i] 
(2) i = i + 1 (i++)  after (1)  
(4) t3 = a1 & 0xFF   after (1) 
(6) t5 = t3 << 4   after (4) 
(A) v = t5     after (6) 
------ Sequence Point 
(3) a2 = p[i] 
(5) t4 = a2 & 0xF0000000 after (3) 
(7) t6 = t4 >> 28   after (5) 
(8) t7 = v | t6   after (7) 
(9) v = t7    after (8) 

Ici, tout est bien défini lorsque l'écriture sur i est appliquée en place et n'est pas relue dans la même expression.

Règle simple. n'utilisez pas les opérateurs ++ ou - dans une expression plus grande. Votre code est tout aussi lisible comme ceci:

++i; // prefer pre-increment (it makes no difference here, but is a useful habit) 
v = ((p[ i ] & 0xFF) << 4 | (p[ i ] & 0xF0000000) >> 28; 

Voir cet article pour une explication détaillée de l'ordre d'évaluation:
What are all the common undefined behaviours that a C++ programmer should know about?

+0

+1: Très bonne explication! Mais ta dernière suggestion est fausse! Il doit être: v = ((p [i] & 0xFF) << 4 | (p [i + 1] & 0xF0000000) >> 28; ++ i; – mmmmmmmm

+2

@rstevens Ma dernière expression dépend de ce que 'Bjoern' essayait de faire, puisque son code actuel a un comportement indéfini qui décide de ce que le code 'doit être' dépendra de la façon dont 'Bjoern' interprétera ce que ++ faisait, et donc sans autre contexte il y a plusieurs veriants différents Je vois mon interprétation comme l'un de ces veriants et je suis sûr que 'Bjoern' peut extrapoler ce qu'il veut faire à partir de là –

+0

PS Je suis d'accord que le ++ je pourrais potentiellement aller après l'expression, mais je ne suis pas d'accord le deuxième accès à p a besoin de +1 p [i +1] comme dans votre expression 'doit être', votre réponse 'doit donc être fausse' ;-) Voir les absolus sont rarement corrects. –

9

Le premier exemple est un comportement non défini. Vous ne pouvez pas lire une variable plus d'une fois dans une expression qui modifie également la valeur de la variable. Voir this (entre autres sur Internet).

+0

Vous ne pouvez même lire l'un dans tous les cas. (a [b] = (b = 5) par exemple est indéfini) – AProgrammer

+0

Cela le lit réellement deux fois. L'expression (b = 5) a la valeur de b, donc b est lu une fois en tant qu'index de tableau sur le côté gauche, et de nouveau comme la valeur de l'expression (b = 5) sur la droite. –

+0

Il n'est pas lu. La valeur de b = 5 est la valeur écrite, pas une valeur lue après l'écriture 5. (Cela pourrait faire la différence si b est volatile). – AProgrammer

3

Parfois avant la fin de l'expression.

Il n'est pas défini de lire un objet qui est également modifié pour autre chose que de déterminer la nouvelle valeur car il n'est pas défini pour écrire un objet deux fois. Et vous pouvez même obtenir une valeur inconsistant (c'est-à-dire lire quelque chose qui n'est ni l'ancienne ni la nouvelle valeur).

13

i est incrémenté quelque temps avant le point de séquence suivant. Le seul point de séquence dans l'expression que vous avez donné est à la fin de l'énoncé - ainsi, «un jour avant la fin de l'énoncé» est la réponse dans ce cas. C'est pourquoi vous ne devriez pas modifier une lvalue et lire sa valeur sans un point de séquence intermédiaire - le résultat est indéterminé.

& &, ||, virgule et? les opérateurs introduisent des points de séquence, ainsi que la fin d'une expression et un appel de fonction (ce dernier signifie que si vous faites f (i ++, & i), le corps de f() verra la valeur mise à jour s'il utilise le pointeur examinez i).

+0

Vous pouvez modifier un objet et le lire si vous le lisez pour déterminer la nouvelle valeur. Dans ce cas, vous pouvez le lire plusieurs fois (b = b + b; est valide). Notez que vous ne pouvez pas faire f (i ++, i), la virgule n'est pas ici l'opérateur virgule. – AProgrammer

1

Votre expression a un comportement indéfini, voir par exemple this sur les points de séquence dans les instructions C et C++.

Questions connexes