i = ++ i + ++ i; en C ++
-
19-08-2019 - |
Pregunta
¿Puede alguien explicarme por qué este código imprime 14? Otro estudiante me preguntó y no pude resolverlo.
int i = 5;
i = ++i + ++i;
cout<<i;
Solución
El orden de los efectos secundarios no está definido en C ++. Además, la modificación de una variable dos veces en una sola expresión no tiene un comportamiento definido (Ver estándar C ++ , & # 167; 5.0.4, página física 87 / página lógica 73).
Solución: no use efectos secundarios en expresiones complejas, no use más de uno en expresiones simples. Y no está de más habilitar todas las advertencias que el compilador puede darle: Agregar -Wall
(gcc) o /Wall /W4
(Visual C ++) a la línea de comando genera una advertencia adecuada:
test-so-side-effects.c: In function 'main':
test-so-side-effects.c:5: warning: operation on 'i' may be undefined
test-so-side-effects.c:5: warning: operation on 'i' may be undefined
Obviamente, el código se compila para:
i = i + 1;
i = i + 1;
i = i + i;
Otros consejos
Ese es un comportamiento indefinido, el resultado variará dependiendo del compilador que use. Consulte, por ejemplo, C ++ FAQ Lite .
En algunas de las respuestas / comentarios ha habido una discusión sobre el significado de "comportamiento indefinido" y si eso invalida el programa. Así que publico esta respuesta bastante larga que detalla exactamente lo que dice el estándar con algunas notas. Espero que no sea demasiado aburrido ...
Los bits citados del estándar provienen del estándar C ++ actual (ISO / IEC 14882: 2003). Hay una redacción similar en el estándar C.
Según el estándar C ++, modificar un valor más de una vez dentro de un conjunto de puntos de secuencia da como resultado un comportamiento indefinido (sección 5, párrafo 4):
Excepto donde se indique, el orden de evaluación de operandos de individuo operadores y subexpresiones de expresiones individuales y el orden en el que se producen los efectos secundarios, es sin especificar.53) Entre las anteriores y la siguiente secuencia apunta un escalar objeto tendrá su valor almacenado modificado a lo más una vez por el evaluación de una expresión. Además, el valor anterior será accedido solo para determinar el valor para ser almacenado. Los requisitos de esto el párrafo se cumplirá para cada orden permisible de la subexpresiones de una expresión completa; de lo contrario, el comportamiento es indefinido. [Ejemplo:
i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented
& # 8212; final del ejemplo]
Tenga en cuenta que el segundo ejemplo, " i = 7, i++, i++;
" se define ya que el operador de coma es un punto de secuencia.
Esto es lo que el estándar de C ++ dice que significa "comportamiento indefinido":
1.3.12 comportamiento indefinido [defns.undefined]
comportamiento, tal como podría surgir con el uso de una construcción de programa errónea o datos erróneos, para lo cual esto Norma Internacional no impone requisitos. También se puede esperar un comportamiento indefinido cuando esto Norma Internacional omite la descripción de cualquier definición explícita de comportamiento. [Nota: permisible indefinido el comportamiento varía desde ignorar la situación por completo con resultados impredecibles, hasta comportarse durante traducción o ejecución del programa de manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), a la terminación de una traducción o ejecución (con la emisión de un mensaje de diagnóstico). Muchas construcciones de programa erróneas no generan un comportamiento indefinido; están obligados a ser diagnosticados. ]
En otras palabras, el compilador es libre de hacer lo que quiera, incluyendo
- escupiendo un mensaje de error,
- haciendo algo implementación definida & amp; documentado,
- tener resultados completamente impredecibles
El segundo elemento cubre las extensiones de idioma que tienen la mayoría de los compiladores, pero, por supuesto, no están definidas en el estándar.
Así que supongo que, estrictamente hablando, algo que exhibe un comportamiento indefinido no es 'ilegal', pero en mi experiencia siempre que ha habido algo en un programa C / C ++ que exhibe un 'comportamiento indefinido' (a menos que sea una extensión), es un error . Creo que llamar ilegal a tal construcción no es confuso, engañoso o equivocado.
Además, creo que tratar de explicar lo que está haciendo el compilador para alcanzar el valor 14 no es particularmente útil, ya que eso pierde el sentido. El compilador podría estar haciendo casi cualquier cosa; de hecho, es probable que el compilador pueda alcanzar un resultado diferente cuando se ejecuta utilizando diferentes opciones de optimización (o puede producir código que falla, ¿quién sabe?).
Para aquellos que desean algunas referencias adicionales o una apelación a la autoridad, aquí hay algunos consejos:
Respuesta larga y larga de Steve Summit (responsable de las preguntas frecuentes comp.lang.c) sobre este tema desde 1995:
Esto es lo que Bjarne Stroustrup tiene que decir al respecto:
Nota al pie : el estándar C ++ usa la palabra 'ilegal' exactamente una vez, cuando describe una diferencia entre C ++ y el Estándar C con respecto al uso de static
o extern
con declaraciones de tipo.
Simple ... su compilador está evaluando AMBOS incrementos antes de realizar la suma, sin almacenar en caché los resultados intermedios. Esto significa que cuando agrega i dos veces, ahora tiene el valor de 7.
Si lo haces
int j=++i;
int k=++i;
i = j+k;
verá 13 como se esperaba.
En su compilador particular, está eligiendo hacer las dos operaciones ++ primero, luego la adición. Está interpretando el código como:
int i = 5;
++i;
++i;
i = i + i;
cout << i;
Eso es perfectamente válido.
Una mejor pregunta sería, ¿siempre será 14
?
int i = 5;
i = ++i + ++i;
cout<<i;
i = ++i + ++i ;
i = ++(5) + ++(5) ;
i = 6 + 6 ;
i = 12;
i = ++i + ++i ;
i = ++i + ++(5) ;
i = ++i + (6) ;
i = ++(6) + 6 ;
i = (7) + 6 ;
i = 13;
i = ++i + ++i ;
i = ++i + ++(5) ;
i = ++(6) + (6) ;
i = (7) + (7) ;
i = 14;
Con toda probabilidad, probablemente sea <=>, porque tiene un poco más sentido.
Creo que al mirar el problema desde la vista del árbol de sintaxis, la respuesta al problema se vuelve más clara:
i
|
=
|
+
|
expresión unaria - expresión unaria
expresión unaria: expresión de operador unario
En nuestro caso, la expresión se reduce a la variable i.
Ahora, lo que sucede es que ambas expresiones unarias modifican el mismo operando, por lo que el código hace dos veces ++ i al evaluar las expresiones unarias antes de agregar los resultados de ambas expresiones unitarias.
Entonces, lo que hace el código es de hecho
++ i;
++ i;
i = i + i;
Para i = 5 eso significa
i = i + 1; // i < - 6
i = i + 1; // i < - 7
i = i + i; // i < - 14
Porque el incremento del prefijo tiene prioridad:
int i = 5;
i = i+1; // First ++i, i is now 6
i = i+1; // Second ++i, i is now 7
i = i + i // i = 7 + 7
cout << i // i = 14
i = i++ + i; //11
i = i++ + i++; //12
i = i++ + ++i; //13
i = ++i + i++; //13
i = ++i + ++i; //14