studio reales proyectos programacion libro introducción incluye herramientas fundamentos fuente español código con avanzado aplicaciones c++ standards variable-assignment

c++ - reales - ¿Por qué el comportamiento ''i=++ i+1` no especificado?



libro de android studio en español pdf (15)

Considere la siguiente cita de C ++ Standard ISO / IEC 14882: 2003 (E) (sección 5, párrafo 4):

Excepto cuando se indique lo contrario, el orden de evaluación de operandos de operadores individuales y las subexpresiones de expresiones individuales, y el orden en que se producen los efectos secundarios, no se especifica. 53) Entre el punto de secuencia anterior y siguiente, un objeto escalar debe tener su valor almacenado modificado como máximo una vez por la evaluación de una expresión. Además, se accederá al valor anterior solo para determinar el valor que se almacenará. Los requisitos de este párrafo se cumplirán para cada orden permisible de las subexpresiones de una expresión completa; de lo contrario, el comportamiento no está definido. [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

-Final ejemplo]

Me sorprendió que i = ++i + 1 ofrezca un valor indefinido de i . ¿Alguien sabe de una implementación de compilador que no da 2 para el siguiente caso?

int i = 0; i = ++i + 1; std::cout << i << std::endl;

El caso es que operator= tiene dos argumentos. El primero siempre i referencia. El orden de evaluación no importa en este caso. No veo ningún problema excepto C ++ Standard tabú.

Por favor , no considere tales casos donde el orden de los argumentos es importante para la evaluación. Por ejemplo, ++i + i obviamente no está definido. Por favor, considera solo mi caso i = ++i + 1 .

¿Por qué el estándar de C ++ prohíbe tales expresiones?


i = v [i ++]; // el comportamiento no está especificado
i = ++ i + 1; // el comportamiento no está especificado

Todas las expresiones anteriores invocan un comportamiento no definido.

i = 7, i ++, i ++; // me hago 9

Esto esta bien.

Lea las C-preguntas frecuentes de Steve Summit.


Actualización para C ++ 11 (30/09/2011)

Para , esto está bien definido en C ++ 11. No se definió solo en C ++ 03, pero C ++ 11 es más flexible.

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

Después de esa línea, seré 2. La razón de este cambio fue ... porque ya funciona en la práctica y hubiera sido más trabajo para que no esté definido que para dejarlo definido en las reglas de C ++ 11 (en realidad, que esto funcione ahora es más un accidente que un cambio deliberado, ¡así que no lo haga en su código!).

Directo de la boca del caballo

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637


Teniendo en cuenta dos opciones: definida o indefinida, ¿qué elección habría hecho?

Los autores del estándar tenían dos opciones: definir el comportamiento o especificarlo como indefinido.

Dada la naturaleza claramente imprudente de escribir dicho código en primer lugar, no tiene sentido especificar un resultado para él. Uno querría desalentar el código así y no alentarlo. No es útil o necesario para nada.

Además, los comités de estándares no tienen ninguna forma de forzar a los escritores de compiladores a hacer algo. Si hubieran requerido un comportamiento específico, es probable que el requisito se hubiera ignorado.

También hay razones prácticas, pero sospecho que estaban subordinadas a la consideración general anterior. Pero para el registro, cualquier tipo de comportamiento requerido para este tipo de expresión y tipos relacionados restringirá la capacidad del compilador de generar código, factorizar subexpresiones comunes, mover objetos entre registros y memoria, etc. C ya estaba impedido por la visibilidad débil restricciones Hace mucho tiempo, idiomas como Fortran se dieron cuenta de que los parámetros de alias y globales eran un asesino de optimización y creo que simplemente los prohibieron.

Sé que estabas interesado en una expresión específica, pero la naturaleza exacta de cualquier construcción dada no importa mucho. No va a ser fácil predecir qué hará un generador de código complejo y el lenguaje intenta no requerir esas predicciones en casos tontos.


¿Qué tal si todos acordamos nunca, nunca, escribir un código como este? Si el compilador no sabe lo que quiere hacer, ¿cómo espera que la pobre savia que lo está siguiendo comprenda lo que quería hacer? Poniendo i ++; en su propia línea no te matará.


Asumiendo que estás preguntando "¿Por qué el lenguaje está diseñado de esta manera?".

Usted dice que i = ++i + i es "obviamente indefinido" pero i = ++i + 1 debería dejar i con un valor definido? Francamente, eso no sería muy consistente. Prefiero tener todo perfectamente definido o todo consistentemente no especificado. En C ++ tengo el último. No es una opción terriblemente mala per se - por un lado, le impide escribir código malvado que hace cinco o seis modificaciones en la misma "declaración".


C / C ++ define un concepto llamado puntos de secuencia , que se refiere a un punto en ejecución donde se garantiza que todos los efectos de las evaluaciones previas ya se habrán realizado. Decir que i = ++i + 1 no está definido porque incrementa i y también se asigna a sí mismo, ninguno de los cuales es un punto de secuencia definido solo. Por lo tanto, no se especifica qué sucederá primero.


Desde ++i , debo asignar "1", pero con i = ++i + 1 , se le debe asignar el valor "2". Como no hay un punto de secuencia intermedio, el compilador puede suponer que la misma variable no se escribe dos veces, por lo que estas dos operaciones se pueden realizar en cualquier orden. así que sí, el compilador sería correcto si el valor final es 1.


El motivo subyacente se debe a la forma en que el compilador maneja la lectura y escritura de valores. El compilador tiene permitido almacenar un valor intermedio en la memoria y solo comprometer el valor al final de la expresión. Leemos la expresión ++i como "aumentar i por uno y devolverlo", pero un compilador podría verlo como "cargar el valor de i , agregar uno, devolverlo, y volverlo a poner en la memoria antes de que alguien lo vuelva a usar" Se recomienda al compilador que evite leer / escribir en la ubicación real de la memoria tanto como sea posible, porque eso ralentizaría el programa.

En el caso específico de i = ++i + 1 , sufre en gran parte debido a la necesidad de reglas de conducta consistentes. Muchos compiladores harán lo "correcto" en una situación así, pero ¿qué pasa si uno de los i s fue en realidad un puntero, apuntando a i ? Sin esta regla, el compilador tendría que tener mucho cuidado para asegurarse de que realizara las cargas y las tiendas en el orden correcto. Esta regla sirve para permitir más oportunidades de optimización.

Un caso similar es el de la llamada regla de alias estrictos. No puede asignar un valor (por ejemplo, un int ) a través de un valor de un tipo no relacionado (por ejemplo, un float ) con solo unas pocas excepciones. Esto evita que el compilador tenga que preocuparse de que un poco de float * utilizado cambie el valor de un int , y mejora enormemente el potencial de optimización.


El problema aquí es que el estándar permite a un compilador reordenar por completo una instrucción mientras se está ejecutando. Sin embargo, no se permite reordenar las declaraciones (siempre que dichos resultados de reordenación cambien el comportamiento del programa). Por lo tanto, la expresión i = ++i + 1; puede ser evaluado de dos maneras:

++i; // i = 2 i = i + 1;

o

i = i + 1; // i = 2 ++i;

o

i = i + 1; ++i; //(Running in parallel using, say, an SSE instruction) i = 1

Esto empeora aún más cuando tiene tipos definidos por el usuario arrojados en la mezcla, donde el operador de ++ puede tener cualquier efecto sobre el tipo que desee el autor del tipo, en cuyo caso el orden utilizado en la evaluación es importante.


El siguiente código demuestra cómo puede obtener el resultado incorrecto (inesperado):

int main() { int i = 0; __asm { // here standard conformant implementation of i = ++i + 1 mov eax, i; inc eax; mov ecx, 1; add ecx, eax; mov i, ecx; mov i, eax; // delayed write }; cout << i << endl; }

Imprimirá 1 como resultado.


Es un comportamiento indefinido, no (solo) comportamiento no especificado porque hay dos escrituras en i sin un punto de secuencia intermedio. Es de esta manera, por definición, en la medida en que lo especifica el estándar.

El estándar permite a los compiladores generar código que retrasa las escrituras en el almacenamiento, o desde otro punto de vista, para volver a secuenciar las instrucciones que implementan los efectos secundarios, de la forma que prefiera, siempre que cumpla con los requisitos de los puntos de secuencia.

El problema con esta expresión de enunciado es que implica dos escrituras en i sin un punto de secuencia intermedio:

i = i++ + 1;

Una escritura es para el valor del valor original de "más uno" y la otra es para ese valor "más uno" de nuevo. Estas escrituras pueden ocurrir en cualquier orden o explotar completamente hasta donde lo permita el estándar. Teóricamente, esto ofrece a las implementaciones la libertad de realizar reescrituras en paralelo sin molestarse en verificar errores de acceso simultáneos.


La parte importante del estándar es:

su valor almacenado modificado como máximo una vez por la evaluación de una expresión

Modifica el valor dos veces, una vez con el operador ++, una vez con la asignación


Tenga en cuenta que su copia del estándar está desactualizada y contiene un error conocido (y fijo) solo en las líneas de código 1 y 3 de su ejemplo, consulte:

Índice de Cuestiones de Lenguaje Estándar Estándar de C ++, Revisión 67, # 351

y

Andrew Koenig: error del punto de secuencia: ¿no especificado o indefinido?

El tema no es fácil de obtener simplemente leyendo el estándar (que es bastante oscuro :( en este caso).

Por ejemplo, estará bien (o no) definido, no especificado o, en general, de hecho depende no solo de la estructura de la declaración, sino también de los contenidos de la memoria (para ser específicos, valores variables) en el momento de la ejecución, otro ejemplo :

++i, ++i; //ok (++i, ++j) + (++i, ++j); //ub, see the first reference below (12.1 - 12.3)

Por favor, eche un vistazo a (lo tiene todo claro y preciso):

JTC1 / SC22 / WG14 N926 "Análisis del punto de secuencia"

Además, Angelika Langer tiene un artículo sobre el tema (aunque no tan claro como el anterior):

"Puntos de secuencia y evaluación de expresión en C ++"

También hubo una discusión en ruso (aunque con algunas declaraciones aparentemente erróneas en los comentarios y en la publicación en sí):

"Точки следования (puntos de secuencia)"


Argumento por analogía: si piensas en los operadores como tipos de funciones, entonces tiene sentido. Si tuviera una clase con un operator= sobrecargado operator= , su declaración de asignación sería equivalente a algo como esto:

operator=(i, ++i+1)

(El primer parámetro se pasa implícitamente a través de this puntero, pero esto es solo para ilustración).

Para una llamada de función simple, esto es obviamente indefinido. El valor del primer argumento depende de cuándo se evalúa el segundo argumento. Sin embargo, con los tipos primitivos te saldrás con la tuya porque el valor original de i simplemente se sobrescribe; su valor no importa. Pero si estuvieras haciendo otra magia en tu propio operator= , entonces la diferencia podría salir a la superficie.

En pocas palabras: todos los operadores actúan como funciones y, por lo tanto, deben comportarse de acuerdo con las mismas nociones. Si i + ++i no está definido, entonces i = ++i debería estar indefinido.


Comete el error de pensar en operator= como una función de dos argumentos , donde los efectos secundarios de los argumentos deben evaluarse por completo antes de que comience la función. Si ese fuera el caso, entonces la expresión i = ++i + 1 tendría múltiples puntos de secuencia, y ++i sería completamente evaluado antes de que comenzara la tarea. Sin embargo, ese no es el caso. Lo que se evalúa en el operador de asignación intrínseca , no es un operador definido por el usuario. Solo hay un punto de secuencia en esa expresión.

El resultado de ++i se evalúa antes de la asignación (y antes del operador de suma), pero el efecto secundario no se aplica necesariamente de inmediato. El resultado de ++i + 1 es siempre el mismo que i + 2 , por lo que ese es el valor asignado a i como parte del operador de asignación. El resultado de ++i siempre es i + 1 , entonces eso es lo que se asigna a i como parte del operador de incremento. No hay punto de secuencia para controlar qué valor debe asignarse primero.

Como el código está violando la regla de que "entre el punto de secuencia anterior y siguiente un objeto escalar tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión", el comportamiento no está definido. Prácticamente , sin embargo, es probable que primero se asigne i + 1 o i + 2 , luego se le asignará el otro valor y, finalmente, el programa continuará ejecutándose como de costumbre: sin demonios nasales ni inodoros explosivos, y sin i + 3 , ya sea.