2012-01-23 9 views
27

Si el valor de la variable x es inicialmente 0, la expresión x += x += 1 evaluará a 2 en C y a 1 en Javascript.¿Por qué (x + = x + = 1) se evalúa de manera diferente en C y Javascript?

La semántica para C me parece obvia: x += x += 1 se interpreta como x += (x += 1) que es, a su vez, equivalente a

x += 1 
x += x // where x is 1 at this point 

¿Cuál es la lógica detrás de la interpretación de Javascript? ¿Qué especificación impone tal comportamiento? (Cabe señalar, por cierto, que Java está de acuerdo con Javascript aquí).

Actualización: Resulta que la expresión x += x += 1 ha indefinido comportamiento de acuerdo con el estándar C (gracias ouah, John Bode, DarkDust, Drew Dormann), que parece echar a perder todo el sentido de la pregunta para algunos lectores. La expresión se puede hacer conforme a los estándares insertando una función de identidad en ella de la siguiente manera: x += id(x += 1). La misma modificación se puede hacer en el código de Javascript y la pregunta sigue siendo como se indica. Suponiendo que la mayoría de los lectores puedan entender el punto detrás de la formulación "no conforme a los estándares", lo mantendré porque es más conciso.

Actualización 2: Resulta que, según C99, la introducción de la función de identidad probablemente no resuelva la ambigüedad. En este caso, querido lector, considere la pregunta original como C++ en vez de C99, donde "+ =" probablemente ahora sea considerado seguramente como un operador sobrecargable con una secuencia de operaciones definida de manera única. Es decir, x += x += 1 ahora es equivalente a operator+=(x, operator+=(x, 1)). Perdón por el largo camino hacia el cumplimiento de las normas.

+34

Es una expresión con múltiples efectos secundarios interrelacionados. ¿Por qué eso importa? No escriba código así. –

+0

En JavaScript, toma cero, lo agrega a sí mismo y luego agrega uno. No estoy seguro de qué 'especificación 'hace este comportamiento, pero parece tener sentido para mí leer de izquierda a derecha. – Jasper

+0

@Jasper. El operador + = tiene una asociación correcta. Por lo tanto, debe leerlo de derecha a izquierda, como x + = (x + = 1). Para simplificar, podemos asumir que he puesto esos corchetes allí. –

Respuesta

15

JavaScript y Java tienen reglas de evaluación de izquierda a derecha bastante estrictas para esta expresión. C no lo hace (incluso en la versión que proporcionó que tiene la función de identidad interpuesta).

La especificación ECMAScript tengo (3ª edición, que voy a admitir es bastante antiguo - la versión actual se puede encontrar aquí: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) dice que los operadores de asignación compuestos son evaluados de esta manera:

11,13. 2 Compuesto Asignación (op =)

El AssignmentExpression producción: LeftHandSideExpression @ = AssignmentExpression, donde @ representa uno de los operadores indicaron anteriormente, se evalúa como sigue:

  1. Evalúe LeftHandSideExpression.
  2. Llamar a GetValue (Resultado (1)).
  3. Evaluate AssignmentExpression.
  4. Llamada GetValue (Resultado (3)).
  5. Aplicar operador @ al resultado (2) y al resultado (4).
  6. Llamada PutValue (Resultado (1), Resultado (5)).
  7. retorno de resultado (5)

Se cuenta que Java tiene el mismo comportamiento que JavaScript - Creo que su especificación es más fácil de leer, así que voy a publicar aquí algunos fragmentos (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.7):

15,7 orden de evaluación

El lenguaje de programación Java garantiza que los operandos de operadores parecen ser evaluado en un orden de evaluación específico, es decir, de izquierda a derecha.

Se recomienda que el código no dependa fundamentalmente de esta especificación. El código es generalmente más claro cuando cada expresión contiene como máximo un efecto de lado , como su operación más externa, y cuando el código no depende de exactamente qué excepción surge como consecuencia de la evaluación de expresiones de izquierda a derecha .

15.7.1 Evaluar primero el operando de la mano izquierda El operando de la izquierda de un operador binario parece estar completamente evaluado antes de evaluar cualquier parte del operando de la mano derecha . Por ejemplo, si el operando de la izquierda contiene una asignación a una variable y el operando de la derecha contiene una referencia a esa misma variable, el valor producido por la referencia reflejará primero que la asignación ocurrió .

...

Si el operador es un operador compuesto de asignación (§15.26.2), entonces evaluación del operando de la izquierda incluye tanto recordando la variable que el operando de la izquierda y denota buscando y guardando el valor de esa variable para usar en la operación de combinación implícita.

Por otra parte, en el ejemplo de la conducta no indefinido, en que proporciona una función de la identidad intermedia:

x += id(x += 1); 

si bien no es un comportamiento indefinido (ya que la llamada de función proporciona un punto de secuencia), sigue siendo un comportamiento no especificado si el x más a la izquierda se evalúa antes o después de la llamada a la función. Por lo tanto, si bien no es 'todo vale' un comportamiento indefinido, el compilador de C está siendo permitida para evaluar tanto x variables antes de llamar a la función id(), en cuyo caso el valor final almacena en la variable será 1:

Por ejemplo, Si x == 0 empezar, la evaluación podría parecer:

tmp = x; // tmp == 0 
x = tmp + id(x = tmp + 1) 
// x == 1 at this point 

o podría evaluarlo así:

tmp = id(x = x + 1); // tmp == 1, x == 1 
x = x + tmp; 
// x == 2 at this point 

Tenga en cuenta que el comportamiento no especificado es s muy diferente a un comportamiento indefinido, pero aún no es un comportamiento deseable.

+1

Gracias. Como resumo esto para mí ahora, es que Java/Javascript interpreta la expresión 'x + = y' como equivalente a' x = x + y' con la orden de evaluación de izquierda a derecha impuesta, mientras que los compiladores de C/C++ que he intentado (a pesar de que el estándar no es estricto aquí) interpretan la expresión 'x + = y' como equivalente a algo como' operador + = (x, y) '. Me he acostumbrado a la última forma de pensar, por lo tanto, la sorpresa y esta pregunta. –

+4

@KT: No creo que tu resumen del comportamiento de C/C++ sea correcto (deja de lado la sobrecarga de la ópera tor en C++). C evalúa 'x + = y' como' x = x + y' con 'x' siendo evaluado solo una vez (solo es importante si el lado izquierdo tiene un efecto secundario). Pero con un montón de advertencias que son especialmente importantes si la expresión es parte de una expresión más amplia, como el orden de los problemas de evaluación y los problemas de comportamiento indefinido que aparecen en varias respuestas y comentarios. Pareces descontar esos comentarios, pero son esenciales para el hecho de que en C, 'x + = x + = 1' es un código con errores con un resultado sin sentido. –

+1

Entiendo que cualquier estándar en particular es libre de definir las cosas de una forma u otra, o dejarlas sin especificar. Mi pregunta no era si el código está cumpliendo con la norma X, sino más bien sobre la semántica que (podría) producir una u otra conducta. Es fácil observar que, independientemente de las ambigüedades de especificación, la mayoría de los creadores de compiladores C/C++ parecen interpretar las cosas de una manera particular, que, como resulta, no coincidía con la semántica (implícita o explícita) de otros lenguajes con la misma sintaxis "Cómo puede ser" fue mi pregunta. Ahora sé. –

9

En C, x += x += 1 es comportamiento indefinido.

No puede contar con ningún resultado que ocurra de manera consistente porque no está definido intentar actualizar el mismo objeto dos veces entre sequence points.

+5

te creo pero demuéstralo. – Triptych

+3

¿Por qué está recibiendo tantos votos hacia arriba? No veo ni una pizca de evidencia, lo siento! Hay una lista de precedencias y asociatividad de operadores en [Wikipedia] (http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence) y parece que '+ =' tiene asociatividad de derecha a izquierda. Por lo tanto, es podría definirse. Cotizaciones estándar? –

+0

¡Estoy arrestado! (Mis fuentes fueron otras publicaciones SO) Editado ahora. –

27

x += x += 1; es un comportamiento indefinido en C.

La declaración de la expresión viola secuencia señala reglas.

(C99, 6.5p2) "Entre el punto de secuencia anterior y el siguiente, un objeto tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión."

+3

Incluso si se trata de un comportamiento indefinido (que, creo, no es completamente obvio desde el párrafo presentado), lo he probado en un par de compiladores de C y la expresión se comporta como se esperaba. Por lo tanto, mi pregunta es más acerca de lo que Javascript está haciendo aquí. De nuevo, tanto los intérpretes de Chrome como Firefox parecen estar de acuerdo con la interpretación. –

+3

Alguien debería tener en cuenta que esto es solo la mitad de una respuesta. – Triptych

+0

El hecho de que sea un comportamiento indefinido en C significa comparar el resultado con algo definido o indefinido no tiene sentido. – ouah

4

Todas las expresiones de JavaScript se evalúan de izquierda a derecha.

La asociatividad de ...

var x = 0; 
x += x += 1 

habrá ...

var x = 0; 
x = (x + (x = (x + 1))) 

Por lo tanto, debido a su izquierda a derecha evaluación, el valor actual de x serán evaluados antes de cualquier otra operación toma lugar.

El resultado podría ser visto así ...

var x = 0; 
x = (0 + (x = (0 + 1))) 

... que claramente igualarán 1.

Entonces ...

var x = 0; 
    x = (x + (x = (x + 1))); 
// x = (0 + (x = (0 + 1))); // 1 


    x = (x + (x = (x + 1))); 
// x = (1 + (x = (1 + 1))); // 3 


    x = (x + (x = (x + 1))); 
// x = (3 + (x = (3 + 1))); // 7 
5

Al menos en C, esto es un comportamiento indefinido. La expresión x += x+= 1; tiene dos sequence points: uno implícito justo antes de que comience la expresión (es decir: el punto de secuencia anterior), y luego nuevamente en el ;. Entre estos dos puntos de secuencia x se modifica dos veces y esto se establece explícitamente como un comportamiento indefinido según el estándar C99. El compilador es libre de hacer lo que quiera en este punto, incluso hacer que los demonios salgan volando de tu nariz. Si tiene suerte, simplemente hace lo que espera pero simplemente no hay garantía de eso.

Esta es la misma razón por la x = x++ + x++; se indefinida en C. Véase también la C-FAQ para más ejemplos y explicaciones de esto o la StackOverflow C++ FAQ entrada Undefined Behavior and Sequence Points (AFAIK las reglas de C++ para esto son los mismos que para C).

5

Varias cuestiones están en juego aquí.

primero y más importante es esta parte de la especificación del lenguaje C:

6,5 Expresiones
...
2 entre el anterior y el siguiente punto de la secuencia de un objeto tendrá su valor almacenado modificados como máximo una vez mediante la evaluación de una expresión. 72) Además, el valor anterior se leerá solo para determinar el valor que se almacenará. 73)
...
72) Un indicador de estado de coma flotante no es un objeto y se puede configurar más de una vez dentro de una expresión.

73) Este párrafo hace que las expresiones de los estados no definidos como
 
    i = ++i + 1; 
    a[i++] = i; 
al tiempo que permite
 
    i = i + 1; 
    a[i] = i; 

El subrayado es mío.

La expresión x += 1 modifica x (efecto secundario). La expresión x += x += 1 modifica x dos veces sin un punto de secuencia intermedio, y no lee el valor anterior solo para determinar el nuevo valor que se almacenará; por lo tanto, el comportamiento no está definido (es decir, cualquier resultado de es igualmente correcto). Ahora, ¿por qué demonios sería eso un problema? Después de todo, += es asociativo de la derecha, y todo se evalúa de izquierda a derecha, ¿verdad?

Wrong.

3 La agrupación de operadores y operandos se indica mediante la sintaxis. 74) excepción de lo especificado posterior (para la función de llamar (), &&, ||, ?:, y los operadores coma), el orden de evaluación de subexpresiones y el orden en que los efectos secundarios tienen lugar son ambos no especificado.
...
74) La sintaxis especifica la precedencia de los operadores en la evaluación de una expresión, que es el mismo como el orden de los principales apartados de esta subcláusula, la más alta prioridad en primer lugar. Así, por ejemplo, las expresiones permitidas como los operandos del operador binario + (6.5.6) son aquellas expresiones definidas en 6.5.1 a 6.5.6. Las excepciones son expresiones de conversión (6.5.4) como operandos de operadores unitarios (6.5.3), y un operando contenido entre cualquiera de los siguientes pares de operadores: agrupación paréntesis () (6.5.1), soportes de suscripción [] (6.5 .2.1), llamar a la función paréntesis () (6.5.2.2) y el operador condicional ?: (6.5.15).

Énfasis mío.

En general, precedencia y asociatividad no afectan el orden de evaluación o el orden en que se aplican los efectos secundarios. Aquí hay una secuencia de evaluación posible:

t0 = x + 1 
t1 = x + t0 
x = t1 
x = t0 

Oops. No es lo que queríamos

Ahora, otros lenguajes como Java y C# (y estoy asumiendo Javascript) do especifican que los operandos siempre se evalúan de izquierda a derecha, por lo que siempre hay un orden de evaluación bien definido.

+0

Edité la pregunta para tomar el conformidad con los estándares. Después de todo, esto no es de lo que se trata todo el problema. –

Cuestiones relacionadas