c# .net

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



.net (24)

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 hicieron algunos de mis colegas). Probablemente el compilador crea algún tipo de estructura que hace que i sea ​​cero.

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

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

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


¿Qué está haciendo C #, y el "por qué" de la confusión?

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

Considere 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 igual a SetSum(ref i, Inc(ref i)) . El valor de i después de esta declaración 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 está haciendo C # ... sino también por qué mucha gente se confundió con eso ... incluyéndome a mí.


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;



Desmontaje 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

Compila al 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;

Desmontaje del segundo código (solo para probar 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

Apertura de ventana de desmontaje

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

Use esto mientras depura:

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

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

El postfix ++ indica que nos gustaría incrementar el valor del operando después de la evaluación ... que todos saben ... 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) la segunda i se ve afectada
  • Otros usos en la misma línea respetan el operador de cortocircuito como || y && :
    • (false && i++ != i) || i == 0 (false && i++ != i) || i == 0 el tercero no me afecta 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. Almacenar i + i (que es 0 + 0)
  2. Incremento i (i se convierte en 1)
  3. Asigne 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. La tercera i se ve afectada por el incremento.

El orden de evaluación es:

  1. Tienda i (que es 0)
  2. Incremento i (i se convierte en 1)
  3. Almacenar valor del paso 1 + i (que es 0 + 1)
  4. Asigne el valor del paso 3 a i (i se convierte en 1)

El método de post-incremento se ve algo como esto

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

Básicamente, cuando llama a i++ , i es un incremento, pero el valor original se devuelve en su caso, se está devolviendo 0.


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


El operador de incremento posterior a la corrección, ++ , asigna a la variable un valor en la expresión y luego realiza el incremento que asignó devolvió el valor cero (0) a i nuevamente, que sobrescribe el incrementado a uno (1) , por lo que obtiene cero. Puede leer más sobre el operador de incremento en ++ Operator (MSDN).


Esperando contestar esto desde una programación en C 101 tipo de perspectiva.

Me parece que está sucediendo en este orden:

  1. i se evalúa como 0, lo que da como resultado i = 0 + 0 con la operación de incremento i++ "en cola", pero la asignación de 0 a i tampoco se ha realizado.
  2. El incremento i++ ocurre
  3. La asignación i = 0 desde arriba ocurre, sobrescribiendo efectivamente todo lo que hubiera hecho el # 2 (el post-incremento).

Ahora, el # 2 nunca puede suceder (¿probablemente no?) Porque el compilador probablemente se dará cuenta de que no tendrá ningún propósito, pero esto podría depender del compilador. De cualquier manera, otras respuestas más informadas han demostrado que el resultado es correcto y se ajusta al estándar de C #, pero no se define lo que sucede aquí para C / C ++.

Lo que me confunde es cómo y por qué está más allá de mi experiencia, pero el hecho de que la asignación del lado derecho de la mano de la evaluación que se evaluó anteriormente ocurre después del incremento posterior es probablemente lo que nos confunde.

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


Esta:

int i = 0; i += i++

Puede verse como lo hace (lo 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 sucede 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 decremento postfix de la forma x ++ o x-- consta de los siguientes pasos:

  • Si x se clasifica como una variable:

    • Se evalúa x 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 prioridad , el postfix ++ produce antes de += , pero el resultado termina sin ser utilizado (como se usa el valor anterior de i ).

Una descomposición más completa de i += i++ a las partes de las que está hecho requiere que uno sepa que ambos += y ++ no son atómicos (es decir, ninguno de ellos es una sola operación), incluso si parece que lo son. La forma en que se implementan incluye variables temporales, copias de i antes de que se realicen las operaciones, una para cada operación. ( iAdd los nombres iAdd e iAssign para las variables temporales utilizadas 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;


Esto es simplemente de izquierda a derecha, de abajo hacia arriba evaluación del árbol de sintaxis abstracta. Conceptualmente, el árbol de la expresión se recorre de arriba hacia abajo, pero la evaluación se desarrolla a medida que la recursión vuelve a subir el á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. A continuación, se debe evaluar el lado derecho.

El lado derecho es un operador ++ post-incremento. Tiene un operando, i que se evalúa como fuente de un valor y como un lugar donde se almacena un valor. El operador evalúa i , encuentra 0 y, por lo tanto, 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 está de vuelta 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 ) así como el valor anterior, y tiene el valor agregado al valor anterior, es decir, 0 . Entonces, termino con cero.

Al igual que Java, C # ha saneado un aspecto muy básico del lenguaje C al fijar el orden de evaluación. De izquierda a derecha, de abajo hacia arriba: el orden más obvio que es probable que esperen los programadores.


Hay dos opciones:

La primera opción: si el compilador lee la declaración como sigue,

i++; i+=i;

entonces el resultado es 2.

por

else if i+=0; i++;

el resultado es 1


Hay un montón de excelente razonamiento en las respuestas anteriores, solo hice una pequeña prueba y quiero compartir con ustedes

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

Aquí el resultado i está mostrando 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 a primera vista, la respuesta es 1, y la respuesta de i para este es 1.

Caso 2:

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

aquí el operador de incremento no viene en la ruta de ejecución, a diferencia del caso anterior donde i ++ tiene la oportunidad de ejecutarse antes de la adición.

Espero que esto ayude un poco. Gracias


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

Ok, antes de que todos ustedes me quemen ..

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

Estuve tentado a votar a la baja cada una de sus respuestas, pero el impacto de reputación que calculé sería demasiado alto.

¿Por qué estoy tan enojado con ustedes? no por lo que sus respuestas explican ...
Quiero decir, cada respuesta que leí hizo un esfuerzo extraordinario para explicar lo imposible, ¡lo aplaudo!

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

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

Todos ustedes están equivocados!

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

un error en el mecanismo de evaluación del idioma si lo desea ... 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 este ... es el resultado intuitivo! porque primero evaluamos t asignamos un valor y solo después de la evaluación y la asignación tuvimos la operación posterior, ¿no es racional?

¿es racional que: i=i++ e i=i produzcan el mismo resultado para i ?

mientras que t=i++ y t=i tienen diferentes resultados para i .

La operación posterior es algo que debe suceder después de la evaluación de la declaración.
Por lo tanto:

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

Debería ser lo mismo si escribiéramos:

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 complaciente o un error en el diseño del lenguaje si optamos por un pensamiento racional. Sin embargo, MSDN y muchas otras fuentes nos dicen "hey, ¡esto no está definido!"

Ahora, antes de continuar, incluso este conjunto de ejemplos que proporcioné no está respaldado ni reconocido por nadie. Sin embargo, esto es lo que, de acuerdo con el modo intuitivo y racional, debería haber sido el resultado.

¡El programador no debe saber cómo se escribe o traduce el ensamblaje!

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

Y para terminar copié esto de Wikipedia, Incremento y decremento a los operadores :
Dado que 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 deben realizarse los operadores de resta e incremento. Situaciones como esta se empeoran cuando el compilador aplica optimizaciones, lo que podría hacer que el orden de ejecución de las operaciones sea diferente de lo que pretendía el programador.

Y por lo tanto.

La respuesta correcta es que esto NO DEBE SER USADO! (¡Como está sin definir!)

Sí ...: tiene resultados impredecibles, incluso si el que cumple C # intenta normalizarlo de alguna manera.

No encontré ninguna documentación de C # que describiera el comportamiento que todos ustedes documentaron como un comportamiento normal o bien definido del lenguaje. ¡Lo que encontré es exactamente lo contrario!

[ Copiado de la documentación de MSDN para los Operadores de Incremento y Disminución de Postfix: ++ y - ]

Cuando se aplica un operador de posfijo a un argumento de función, no se garantiza que el valor del argumento se incremente o disminuya antes de pasar a la función. Vea la sección 1.9.17 en el estándar de C ++ para más información.

Note esas palabras no garantizadas ...

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


La respuesta es que seré 1 .

Veamos cómo:

Inicialmente i=0; .

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

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


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 compilador
  4. i=0+i++ // i sustitución de valor
  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 y luego incremente el valor i en 1. Entonces usa el valor i , 0, mientras calcula i++ y luego lo incrementa en 1. Por lo que resulta en un valor de 0.


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


Primero, i++ devuelve 0. Luego i se incrementa en 1. Finalmente, i se establece en el valor inicial de i que es 0 más el valor i++ devuelto, que también es cero. 0 + 0 = 0.


Simplemente pon,

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

Lo que desea es ++ i, para que agregue 1 a "i" antes de que se ejecute el operador "+ =".


Tenga mucho cuidado: lea las FAQ C: lo que está tratando de hacer (mezcla de asignación y ++ de la misma variable) no solo no se especifica, sino que también está indefinido (lo que significa que el compilador puede hacer algo al evaluar). solo dando resultados "razonables").

Por favor, lea la sección 3 . ¡Toda la sección merece una lectura! 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 compiladores internos, puedes obtener 0, o 2, o 1, ¡o incluso cualquier otra cosa! Y como no está definido, está bien que lo hagan.


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

Si ++ permanece después de la expresión, devuelve el valor que era antes . Así que el siguiente código

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

es 1, porque a era 1 antes de que se incrementara por la posición ++ después de a . La gente llama a esta notación fija de correos . También hay una notación de corrección previa , donde las cosas son exactamente lo contrario: si ++ está delante , la expresión devuelve el valor que está después de la operación:

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

b es dos aquí.

Así que para tu 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 "Programación efectiva de C ++". Internamente, i++ (postfix) recuerda el valor que era, y llama a la notación de prefijo ( ++i ) y devuelve el valor anterior, i . Esta es la razón por la que siempre debe usar ++i for bucles (aunque creo que todos los compiladores modernos están traduciendo i++ a ++i for bucles).


i ++ significa: devolver el valor de i ENTONCES incrementarlo.

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

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

  1. ¿Cuál es el valor actual de i? Es 0. Guárdelo para que podamos agregarle el resultado de i ++.
  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. (sumar 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 incremente.

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

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


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

i += ++i; lo hará antes


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

Entonces 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 de 0.

i+=++i i=2


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

se evalúa de la siguiente manera:

Stack<int> stack = new Stack<int>(); 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 instrucción += .

Pero los operandos de la declaración += 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 += ).