Para i = 0, ¿por qué (i + = i ++) es igual a 0?

Tome el siguiente código (utilizable como una aplicación de consola):

static void Main(string[] args) { int i = 0; i += i++; Console.WriteLine(i); Console.ReadLine(); } 

El resultado de i es 0. Esperaba 2 (como lo hicieron algunos de mis colegas). Probablemente el comstackdor crea algún tipo de estructura que resulta en que i cero.

La razón por la que esperaba 2 es que, en mi opinión, la statement de la mano derecha sería evaluada primero, incrementando i con 1. De lo que se agrega a i. Como i ya es 1, está agregando 1 a 1. Entonces 1 + 1 = 2. Obviamente esto no es lo que está sucediendo.

¿Puedes explicar qué hace el comstackdor o qué sucede en el tiempo de ejecución? ¿Por qué el resultado es cero?

Algún tipo de exención de responsabilidad: soy totalmente consciente de que no usará (y probablemente no debería) este código. Sé que nunca lo haré. Sin embargo, me parece interesante saber por qué actúa de esa manera y qué está sucediendo exactamente.

Esta:

 int i = 0; i += i++ 

Puede verse como usted (la siguiente es una simplificación excesiva):

 int i = 0; i = i + i; // i=0 because the ++ is a postfix operator and hasn't been executed i + 1; // Note that you are discarding the calculation result 

Lo que realmente ocurre es más complicado que eso: eche un vistazo a MSDN, 7.5.9 operadores de incremento y decremento de Postfix :

El procesamiento en tiempo de ejecución de una operación de incremento o disminución de postfix del formulario x ++ o x– consta de los siguientes pasos:

  • Si x está clasificado como una variable:

    • x se evalúa para producir la variable.
    • El valor de x se guarda.
    • El operador seleccionado se invoca con el valor guardado de x como su argumento.
    • El valor devuelto por el operador se almacena en la ubicación dada por la evaluación de x.
    • El valor guardado de x se convierte en el resultado de la operación.

Tenga en cuenta que, debido al orden de precedencia , el postfix ++ ocurre antes de += , pero el resultado termina sin usarse (ya que se usa el valor anterior de i ).


Una descomposición más completa de i += i++ en las partes de las que está compuesta requiere que uno sepa que ni += ni ++ son atómicos (es decir, que ninguno de los dos es una operación única), incluso si se parecen a ellos. La forma en que se implementan implica variables temporales, copias de i antes de las operaciones, una para cada operación. ( iAdd los nombres iAdd e iAssign para las variables temporales usadas para ++ y += respectivamente).

Entonces, una aproximación más cercana a lo que está sucediendo sería:

 int i = 0; int iAdd = i; // Copy of the current value of i, for ++ int iAssign = i; // Copy of the current value of i, for += i = i + 1; // i++ - Happens before += due to order of precedence i = iAdd + iAssign; 

Desassembly del código de ejecución:

 int i = 0; xor edx, edx mov dword ptr i, edx // set i = 0 i += i++; mov eax, dword ptr i // set eax = i (=0) mov dword ptr tempVar1, eax // set tempVar1 = eax (=0) mov eax, dword ptr i // set eax = 0 ( again... why??? =\ ) mov dword ptr tempVar2, eax // set tempVar2 = eax (=0) inc dword ptr i // set i = i+1 (=1) mov eax, dword ptr tempVar1 // set eax = tempVar1 (=0) add eax, dword ptr tempVar2 // set eax = eax+tempVar2 (=0) mov dword ptr i, eax // set i = eax (=0) 

Código equivalente

Se comstack con el mismo código que el siguiente código:

 int i, tempVar1, tempVar2; i = 0; tempVar1 = i; // created due to postfix ++ operator tempVar2 = i; // created due to += operator ++i; i = tempVar1 + tempVar2; 

Desassembly del segundo código (solo para demostrar que son iguales)

 int i, tempVar1, tempVar2; i = 0; xor edx, edx mov dword ptr i, edx tempVar1 = i; // created due to postfix ++ operator mov eax, dword ptr i mov dword ptr tempVar1, eax tempVar2 = i; // created due to += operator mov eax, dword ptr i mov dword ptr tempVar2, eax ++i; inc dword ptr i i = tempVar1 + tempVar2; mov eax, dword ptr tempVar1 add eax, dword ptr tempVar2 mov dword ptr i, eax 

Abrir la ventana de desassembly

La mayoría de las personas no saben, o incluso no recuerdan, que pueden ver el código de ensamblaje final en memoria, usando la ventana Desarmado de Visual Studio. Muestra el código máquina que se está ejecutando, no es CIL.

Úselo mientras depura:

Debug (menu) -> Windows (submenu) -> Disassembly

Entonces, ¿qué está pasando con postfix ++?

El postfix ++ dice que nos gustaría incrementar el valor del operando después de la evaluación … que todo el mundo sabe … lo que confunde un poco es el significado de “después de la evaluación” .

Entonces, ¿qué significa “después de la evaluación” ?

  • otros usos del operando, en la misma línea de código deben verse afectados:
    • a = i++ + i el segundo i se ve afectado por el incremento
    • Func(i++, i) el segundo i está afectado
  • otros usos en la misma línea respetan al operador de cortocircuito como || y && :
    • (false && i++ != i) || i == 0 (false && i++ != i) || i == 0 el tercero i no se ve afectado por i ++ porque no se evalúa

Entonces, ¿cuál es el significado de: i += i++; ?

Es lo mismo que i = i + i++;

El orden de evaluación es:

  1. Tienda i + i (es decir, 0 + 0)
  2. Incremento i (i se convierte en 1)
  3. Asigna el valor del paso 1 a i (i se convierte en 0)

No es que el incremento se esté descartando.

¿Cuál es el significado de: i = i++ + i; ?

Esto no es lo mismo que el ejemplo anterior. El 3 ° i se ve afectado por el incremento.

El orden de evaluación es:

  1. Tienda i (eso es 0)
  2. Incremento i (i se convierte en 1)
  3. Almacenar el valor del paso 1 + i (es decir, 0 + 1)
  4. Asigna el valor del paso 3 a i (i se convierte en 1)
 int i = 0; i += i++; 

se evalúa de la siguiente manera:

 Stack stack = new Stack(); int i; // int i = 0; stack.Push(0); // push 0 i = stack.Pop(); // pop 0 --> i == 0 // i += i++; stack.Push(i); // push 0 stack.Push(i); // push 0 stack.Push(i); // push 0 stack.Push(1); // push 1 i = stack.Pop() + stack.Pop(); // pop 0 and 1 --> i == 1 i = stack.Pop() + stack.Pop(); // pop 0 and 0 --> i == 0 

es decir, i se cambia dos veces: una vez por la expresión i++ y una vez por la statement += .

Pero los operandos de la statement += son

  • el valor i antes de la evaluación de i++ (lado izquierdo de += ) y
  • el valor i antes de la evaluación de i++ (lado derecho de += ).

Primero, i++ devuelve 0. Entonces, i se incrementa en 1. Por último, i se establece en el valor inicial de i que es 0 más el valor que devuelve i++ , que también es cero. 0 + 0 = 0.

Esto es simplemente de izquierda a derecha, evaluación ascendente del árbol de syntax abstracta. Conceptualmente, el árbol de la expresión se camina de arriba hacia abajo, pero la evaluación se despliega a medida que la recursión emerge del árbol desde la parte inferior.

 // source code i += i++; // abstract syntax tree += / \ i ++ (post) \ i 

La evaluación comienza considerando el nodo raíz += . Ese es el principal constituyente de la expresión. El operando izquierdo de += debe evaluarse para determinar el lugar donde almacenamos la variable, y para obtener el valor anterior que es cero. Luego, el lado derecho debe ser evaluado.

El lado derecho es un operador ++ posterior al incremento. Tiene un operando, que se evalúa como fuente de un valor y como un lugar donde se almacenará un valor. El operador evalúa i , encuentra 0 y, en consecuencia, almacena un 1 en esa ubicación. Devuelve el valor anterior, 0 , de acuerdo con su semántica de devolver el valor anterior.

Ahora el control vuelve al operador += . Ahora tiene toda la información para completar su operación. Conoce el lugar donde almacenar el resultado (la ubicación de almacenamiento de i ) y el valor anterior, y tiene el valor añadido al valor anterior, es decir, 0 . Entonces, termino con cero.

Al igual que Java, C # ha desinfectado un aspecto muy asnal del lenguaje C al arreglar el orden de la evaluación. De izquierda a derecha, de abajo hacia arriba: el orden más obvio que es probable que esperen los codificadores.

Porque i++ primero devuelve el valor, luego lo incrementa. Pero después de que i se establece en 1, lo restablece a 0.

El método de incremento posterior se parece a esto

 int ++(ref int i) { int c = i; i = i + 1; return c; } 

Así que, básicamente, cuando llamas a i++ , i es un incremento, pero el valor original se devuelve en tu caso, se devuelve 0.

Respuesta simple

 int i = 0; i += i++; // Translates to: i = i + 0; // because post increment returns the current value 0 of i // Before the above operation is set, i will be incremented to 1 // Now i gets set after the increment, // so the original returned value of i will be taken. i = 0; 

i ++ significa: devuelve el valor de i ENTONCES increméntalo.

i + = i ++ significa: tome el valor actual de i. Agregue el resultado de i ++.

Ahora, agreguemos en i = 0 como condición inicial. i + = i ++ ahora se evalúa así:

  1. ¿Cuál es el valor actual de i? Es 0. Almacénelo para que podamos agregar el resultado de i ++ a él.
  2. Evaluar i ++ (evalúa a 0 porque ese es el valor actual de i)
  3. Cargue el valor almacenado y agregue el resultado del paso 2 a él. (agregar 0 a 0)

Nota: Al final del paso 2, el valor de i es en realidad 1. Sin embargo, en el paso 3, lo descarta cargando el valor de i antes de que se incrementara.

A diferencia de i ++, ++ i devuelve el valor incrementado.

Por lo tanto, i + = ++ te daría 1.

El operador de incremento post fix, ++ , le da a la variable un valor en la expresión y luego hace que el incremento que asignó le devuelva cero (0) valor i que sobrescribe el incrementado (1) , por lo que obtiene cero. Puede leer más sobre el operador de incremento en ++ Operator (MSDN).

i += i++; será igual a cero, porque hace el ++ después.

i += ++i; lo haremos antes

El ++ postfix evalúa i antes de incrementarlo, y += solo evalúa i una vez.

Por lo tanto, 0 + 0 = 0, ya que i se evalúa y se usa antes de que se incremente, ya que se usa el formato postfix de ++ . Para obtener i incrementó primero, use la forma de prefijo ( ++i ).

(Además, solo una nota: solo debes obtener 1, como 0 + (0 + 1) = 1)

Referencias: http://msdn.microsoft.com/en-us/library/sa7629ew.aspx (+ =)
http://msdn.microsoft.com/en-us/library/36x43w8w.aspx (++)

Lo que C # está haciendo, y el “por qué” de la confusión

También esperaba que el valor fuera 1 … pero algunas exploraciones sobre ese asunto aclararon algunos puntos.

Cosider los siguientes métodos:

  static int SetSum(ref int a, int b) { return a += b; } static int Inc(ref int a) { return a++; } 

Esperaba que i += i++ fuera el mismo que SetSum(ref i, Inc(ref i)) . El valor de i después de esta statement es 1 :

 int i = 0; SetSum(ref i, Inc(ref i)); Console.WriteLine(i); // i is 1 

Pero luego llegué a otra conclusión … i += i++ es en realidad lo mismo que i = i + i++ … así que he creado otro ejemplo similar, usando estas funciones:

  static int Sum(int a, int b) { return a + b; } static int Set(ref int a, int b) { return a = b; } 

Después de llamar a este Set(ref i, Sum(i, Inc(ref i))) el valor de i es 0 :

 int i = 0; Set(ref i, Sum(i, Inc(ref i))); Console.WriteLine(i); // i is 0 

Esto no solo explica lo que C # está haciendo … sino también por qué mucha gente se confundió con él … incluyéndome a mí.

Una buena frase mnemotécnica que siempre recuerdo acerca de esto es la siguiente:

Si ++ aparece después de la expresión, devuelve el valor que tenía antes . Entonces el siguiente código

 int a = 1; int b = a++; 

es 1, porque a era 1 antes de que boosta por el ++ parado después de a . La gente llama a esta notación de corrección de publicación . También hay una notación prefijada , donde las cosas son exactamente lo opuesto: si ++ está antes , la expresión devuelve el valor que es después de la operación:

 int a = 1; int b = ++a; 

b es dos aquí.

Entonces, para su código, esto significa

 int i = 0; i += (i++); 

i++ devuelve 0 (como se describió anteriormente), entonces 0 + 0 = 0 .

 i += (++i); // Here 'i' would become two 

Scott Meyers describe la diferencia entre esas dos notaciones en “Progtwigción efectiva en C ++”. Internamente, i++ (postfix) recuerda el valor que tenía, llama a la notación de prefijo ( ++i ) y devuelve el valor anterior, i . Esta es la razón por la cual siempre debes usar ++i en los bucles (aunque creo que todos los comstackdores modernos están traduciendo i++ a ++i en los bucles).

La única respuesta a su pregunta que es correcta es: porque no está definida.

Ok, antes de que todos me quemen …

Todos respondieron por qué i+=i++ está bien y es lógico dar como resultado i=0 .

Estuve tentado de votar por todas y cada una de sus respuestas, pero la reputación que calculé sería demasiado alta.

¿Por qué estoy tan enojado con ustedes? no por lo que explican tus respuestas …
Quiero decir, cada respuesta que leí hizo un esfuerzo notable para explicar lo imposible, ¡Aplaudo!

Pero, ¿cuál es el resultado? ¿es resultado intuitivo? ¿es aceptable resultado?

Cada uno de ustedes vio al “rey desnudo” y de alguna manera lo aceptó como un rey racional.

¡Estás todo INCORRECTO!

i+=i++; el resultado en 0 no está definido.

un error en el mecanismo de evaluación del lenguaje si … ¡o incluso peor! un error en el diseño.

quieres una prueba? por supuesto que quieres!

int t=0; int i=0; t+=i++; //t=0; i=1

¡Ahora esto … es un resultado intuitivo! porque evaluamos por primera vez que le asignamos un valor y solo después de la evaluación y la asignación tuvimos la operación de publicación ocurriendo, ¿no es así?

¿Es racional que: i=i++ i=i produzco el mismo resultado para i ?

mientras que t=i++ t=i tengo resultados diferentes para i .

La operación de publicación es algo que debería suceder después de la evaluación de la statement.
Por lo tanto:

 int i=0; i+=i++; 

Debería ser lo mismo si escribimos:

 int i=0; i = i + i ++; 

y por lo tanto lo mismo que:

 int i=0; i= i + i; i ++; 

y por lo tanto lo mismo que:

 int i=0; i = i + i; i = i + 1; 

Cualquier resultado que no sea 1 indica un error en el comstackdor o un error en el diseño del lenguaje si vamos con un pensamiento racional; sin embargo, MSDN y muchas otras fonts nos dicen “hey, esto no está definido”.

Ahora, antes de continuar, incluso este conjunto de ejemplos que di no es apoyado ni reconocido por nadie … Sin embargo, esto es lo que según la manera intuitiva y racional debería haber sido el resultado.

¡El codificador no debería saber cómo se está escribiendo o traduciendo el conjunto!

Si está escrito de una manera que no respetará las definiciones de idioma, ¡es un error!

Y para terminar, copié esto de Wikipedia, operadores de incremento y decremento :
Como el operador de incremento / decremento modifica su operando, el uso de dicho operando más de una vez dentro de la misma expresión puede producir resultados indefinidos . Por ejemplo, en expresiones como x – ++ x, no está claro en qué secuencia se deben realizar los operadores de resta e incremento. Situaciones como esta se agravan cuando el comstackdor aplica las optimizaciones, lo que puede hacer que el orden de ejecución de las operaciones sea diferente de lo que pretendía el progtwigdor.

Y por lo tanto.

¡La respuesta correcta es que esto NO DEBE UTILIZARSE! (¡ya que está INDEFINIDO!)

Sí … – Tiene resultados impredecibles incluso si el comstackdor de C # intenta normalizarlo de alguna manera.

No encontré ninguna documentación de C # que describa el comportamiento que todos ustedes documentaron como un comportamiento normal o bien definido del idioma. ¡Lo que sí encontré es exactamente lo opuesto!

[ copiado de la documentación de MSDN para Operadores de Incremento y Disminución de Postfix: ++ y – ]

Cuando se aplica un operador de postfijo a un argumento de función, no se garantiza que el valor del argumento se incremente o decremente antes de pasarlo a la función. Consulte la sección 1.9.17 en el estándar de C ++ para obtener más información.

Observe esas palabras no garantizadas

Perdóname si esa respuesta parece arrogante: no soy una persona arrogante. Solo considero que miles de personas vienen aquí para aprender y las respuestas que leo los confundirán y dañarán su lógica y comprensión del tema.

El operador ++ después de la variable lo convierte en un incremento de postfijo. El incremento ocurre después de todo lo demás en la statement, la adición y la asignación. Si, en cambio, coloca el ++ antes de la variable, sucedería antes de que se evaluara el valor de i, y le daría la respuesta esperada.

Los pasos en el cálculo son:

  1. int i=0 // Inicializado a 0
  2. i+=i++ // Ecuación
  3. i=i+i++ // después de simplificar la ecuación por comstackdor
  4. i=0+i++ // i valor de sustitución
  5. i=0+0 // i ++ es 0 como se explica a continuación
  6. i=0 // Resultado final i = 0

Aquí, inicialmente el valor de i es 0. WKT, i++ no es más que: primero use el valor i luego incremente el valor i en 1. Entonces usa el valor i , 0, mientras calcula i++ y luego lo incrementa en 1. Entonces resulta en un valor de 0.

Hay dos opciones:

La primera opción: si el comstackdor lee la statement de la siguiente manera,

 i++; i+=i; 

entonces el resultado es 2.

por

 else if i+=0; i++; 

el resultado es 1.

Tenga mucho cuidado: lea las Preguntas Frecuentes C: lo que está tratando de hacer (mezclar asignaciones y ++ de la misma variable) no solo no está especificado, sino que también está indefinido (lo que significa que el comstackdor puede hacer cualquier cosa al evaluar !, no solo dando resultados “razonables”).

Por favor, lea, sección 3 . ¡Vale la pena leer toda la sección! Especialmente 3.9, lo que explica la implicación de no especificado. La Sección 3.3 le brinda un resumen rápido de lo que puede, y no puede hacer, con “i ++” y similares.

Dependiendo de los componentes internos del comstackdor, ¡puede obtener 0, o 2, o 1, o incluso cualquier otra cosa! Y como no está definido, está bien que lo hagan.

Esperando responder esto desde un tipo de perspectiva de progtwigción C 101.

Me parece que está sucediendo en este orden:

  1. i se evalúa como 0, lo que resulta en i = 0 + 0 con la operación de incremento i++ “en cola”, pero la asignación de 0 a i tampoco ha sucedido todavía.
  2. El incremento i++ ocurre
  3. La asignación i = 0 de arriba ocurre, sobrescribiendo efectivamente todo lo que el # 2 (el incremento posterior) habría hecho.

Ahora bien, el # 2 puede que nunca suceda (¿probablemente no?) Porque es probable que el comstackdor se dé cuenta de que no servirá para nada, pero esto podría depender del comstackdor. De cualquier manera, otras respuestas más informadas han demostrado que el resultado es correcto y se ajusta al estándar C #, pero no está definido lo que sucede aquí para C / C ++.

Cómo y por qué está más allá de mi experiencia, pero el hecho de que la asignación del lado derecho evaluada anteriormente ocurra después del incremento posterior es probablemente lo que es confuso aquí.

Además, no esperarías que el resultado fuera 2 independientemente a menos que lo ++i lugar de i++ , creo.

Simplemente pon,

i ++, agregará 1 a “i” después de que el operador “+ =” haya completado.

Lo que quiere es ++ i, de modo que agregará 1 a “i” antes de que se ejecute el operador “+ =”.

Hay muchos razonamientos excelentes en las respuestas anteriores. Acabo de hacer una pequeña prueba y quiero compartir contigo.

 int i = 0; i+ = i++; 

Aquí el resultado i muestra 0 resultado. Ahora considere los siguientes casos:

Caso 1:

 i = i++ + i; //Answer 1 

antes pensé que el código anterior se parece a esto, así que al principio la respuesta es 1, y la respuesta real de i para este es 1.

Caso 2

 i = i + i++; //Answer 0 this resembles the question code. 

aquí el operador de incremento no entra en la ruta de ejecución, a diferencia del caso anterior donde i ++ tiene la posibilidad de ejecutar antes de la sum.

Espero que esto ayude un poco. Gracias

 i=0 i+=i i=i+1 i=0; 

Luego, el 1 se agrega a i .

i + = i ++

Entonces, antes de agregar 1 a i , tomé el valor de 0. Solo si agregamos 1 antes, obtengo el valor 0.

 i+=++i i=2 

La respuesta es que seré 1 .

Echemos un vistazo cómo:

Inicialmente i=0; .

Luego, al calcular i +=i++; según el valor de, tendremos algo así como 0 +=0++; , de acuerdo con la precedencia del operador 0+=0 se realizará primero y el resultado será 0 .

Entonces el operador de incremento se aplicará como 0++ , como 0+1 y el valor de i será 1 .