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?
+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
@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í. –
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. –