2009-08-03 13 views
6

Decir que tengo una expresión como esta¿Cuándo se evalúa exactamente el operador de incremento de postfix en una expresión compleja?

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

con p ser un puntero a una matriz de asignación dinámica de 32 bits enteros.

Cuando exactamente se i se incrementa? Me di cuenta de que el código anterior entrega un valor diferente para v que el siguiente código:

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

Mi mejor estimación para este comportamiento es que i no se incrementa antes de evaluar la parte derecha de la anterior |.

Cualquier idea sería apreciada!

Gracias de antemano,

\ Bjoern

Respuesta

13

El problema es el orden de evaluación:
El estándar C++ no define el orden de evaluación de las sub expresiones. Esto se hace para que el compilador sea lo más agresivo posible en las optimizaciones.

Lets descomponerlo:

  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) 

Ahora el compilador es libre de volver a organizar las expresiones de este modo sub siempre que el anterior 'después' de las cláusulas no son violados.Entonces una optimización fácil y rápida es mover 3 arriba de una ranura y luego eliminar expresiones comunes (1) y (3) (ahora uno al lado del otro) son las mismas y así podemos eliminar (3)

Pero el compilador no tiene que hacer la optimización (y es probablemente mejor que yo en eso y tiene otros trucos bajo la manga). Sin embargo, se puede ver cómo el valor de (a1) siempre será lo que se espera, pero el valor de (a2) dependerá de lo que ordene el compilador decide hacer las otras sub-expresiones.

La única garantiza que usted tiene que el compilador no puede moverse sub-expresiones más allá de un punto de secuencia. Su punto de secuencia más común es ';' (el final de la declaración). Hay otros, pero evitaría usar este conocimiento ya que la mayoría de la gente no conoce bien el funcionamiento del compilador. Si escribe código que utiliza trucos de puntos de secuencia, entonces alguien puede volver a factorizar el código para que se vea más legible y ahora su truco acaba de convertirse en comportamiento indefinido.

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) 

Aquí todo está bien definido ya que la escritura en i se demanda en el lugar y no se vuelve a leer en la misma expresión.

regla simple. no use operadores ++ o - dentro de una expresión más grande. Su código es tan legible como esto:

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

Consulte este artículo para obtener una explicación detallada de la orden de evaluación:
What are all the common undefined behaviours that a C++ programmer should know about?

+0

+1: ¡Muy buena explicación! ¡Pero tu última sugerencia es incorrecta! Tiene que ser: v = ((p [i] y 0xFF) << 4 | (p [i + 1] 0xF0000000) >> 28; ++ i; – mmmmmmmm

+2

@rstevens Mi última expresión depende de lo que pase. 'Bjoern' estaba tratando de hacer, ya que su código actual ha sin definir el comportamiento de decidir cuál es el código 'tiene que ser' dependerá de cómo 'Bjoern' interpreta lo que ++ estaba haciendo, y por lo tanto sin más contexto hay múltiples veriants diferentes que pueden estar que pueden ser correctas. veo mi interpretación como uno de estos veriants y estoy seguro 'Bjoern' puede extrapolar lo que quiere hacer a partir de ahí. –

+0

PS. estoy de acuerdo en que el i ++ potencialmente podría ir después de la expresión, pero no estoy de acuerdo que el segundo acceso a p necesita el +1 p [i +1] como en su expresión 'tiene que ser'. Por lo tanto, su respuesta 'tiene que ser' incorrecta. ;-) Ver absolutos son muy correctos. –

9

El primer ejemplo es un comportamiento indefinido. No puede leer una variable más de una vez en una expresión que también cambie el valor de la variable. Consulte this (entre otros lugares en Internet).

+0

Ni siquiera se puede leer en uno en todos los casos. (a [b] = (b = 5) por ejemplo no está definido) – AProgrammer

+0

Eso realmente lo lee dos veces. La expresión (b = 5) tiene el valor de b, por lo que b se lee una vez como un índice de matriz en el lado izquierdo, y luego otra vez como el valor de la expresión (b = 5) a la derecha. –

+0

No se lee. El valor de b = 5 es el valor escrito, no un valor leído después de escribir 5 en él. (Podría hacer una diferencia si b es volátil). – AProgrammer

3

veces antes del final de la expresión.

no está definido para leer un objeto que también se modificó para algo más que para determinar el nuevo valor, ya que no está definido para escribir un objeto dos veces. Y es posible que incluso tenga un valor incoherente (es decir, que lea algo que no sea el anterior ni el nuevo).

13

i se incrementa en algún momento antes de que el siguiente punto de la secuencia. El único punto de secuencia en la expresión que ha dado se encuentra al final de la declaración, por lo que "en algún momento antes del final de la declaración" es la respuesta en este caso.

Es por eso que no se debe tanto modificar un valor izquierdo y leer su valor sin un punto de secuencia intermedia - el resultado es indeterminado.

El & &, ||, coma y? los operadores introducen puntos de secuencia, así como el final de una expresión y una llamada de función (esto último significa que si lo hace f (i ++, & i), el cuerpo de f() verá el valor actualizado si usa el puntero para examinar i).

+0

Puede modificar un objeto y leerlo si lo lee para determinar el nuevo valor. En ese caso, puede leerlo varias veces (b = b + b; es válido). Tenga en cuenta que no puede hacer f (i ++, i), la coma aquí no es el operador de coma. – AProgrammer

1

Su expresión se ha indefinido comportamiento, véase por ejemplo this sobre los puntos de secuencia en las declaraciones de C y C++.

Cuestiones relacionadas