47 votos

¿En C ++ 11, ' me += ++ + 1' exhibir comportamiento indefinido?

Esta pregunta surgió mientras estaba leyendo (las respuestas a) ¿por qué es i = ++i + 1 bien definido en C++11?

Tengo entendido que el sutil explicación es que (1) la expresión ++i devuelve un lvalue pero + toma prvalues como operandos, por lo que una conversión de lvalue a prvalue debe ser realizado; este consiste en la obtención del valor actual de que lvalue (en lugar de uno más que el anterior valor de i) y por lo tanto debe ser secuenciado después de que el efecto secundario de que el incremento (es decir, la actualización de i) (2) del lado izquierdo de la asignación también es un lvalue, por lo que su valor de evaluación no implica ir a buscar el valor actual de i; mientras que este valor de cálculo es unsequenced w.r.t. el valor de cálculo de la RHS, esto no plantea ningún problema (3) el valor de cálculo de la tarea en sí consiste en la actualización i (de nuevo), pero es la secuencia después de que el valor de cálculo de su lado derecho, y por lo tanto, después de la prvious actualización a i; no hay problema.

Bien, así que no hay UB allí. Ahora mi pregunta es ¿qué pasa si uno cambia la asignación de operador de = a += (o una similar operador).

¿La evaluación de la expresión i += ++i + 1 conducir a un comportamiento indefinido?

Como yo lo veo, la norma parece contradecirse a sí mismo aquí. Desde la LHS de += es todavía un lvalue (y su RHS todavía un prvalue), el mismo razonamiento que se aplica en cuanto a (1) y (2) se refiere; no hay un comportamiento indefinido en el evalutation de los operandos en +=. Como para (3), la operación de la asignación compuesta += (más precisamente, el efecto secundario de la operación; su valor en el cálculo, si es necesario, en cualquier caso, es secuenciado después de su efecto secundario) ahora debe tanto recuperar el valor actual de i, y, a continuación, (obviamente secuenciado después de que, aunque la norma no lo dice explícitamente, o de lo contrario la evaluación de dichos operadores siempre invocar un comportamiento indefinido) agregar los RHS y almacenar el resultado en i. Estas operaciones se habría dado un comportamiento indefinido si fueran unsequenced w.r.t. el efecto secundario de la ++, pero como se ha argumentado anteriormente (el efecto secundario de la ++ es la secuencia antes de que el valor de cálculo de + dando la RHS de la += operador, la cual el valor de cálculo es la secuencia de antes de la operación de asignación compuesta), que no es el caso.

Pero por otro lado, la norma también dice que E += F es equivalente a E = E + F, excepto que (lvalue) E se evalúa sólo una vez. Ahora en nuestro ejemplo el valor de cálculo de i (que es lo E ) como lvalue no implica nada que debe ser secuenciado w.r.t. otras acciones, con el fin de hacerlo una o dos veces no hace ninguna diferencia; nuestra expresión debe ser estrictamente equivalente a E = E + F. Pero aquí está el problema: es bastante obvio que la evaluación de i = i + (++i + 1) daría un comportamiento indefinido! ¿Por qué? O es un defecto de la norma?

Añadido. He modificado ligeramente mi discusión anterior, para hacer más justicia a la debida distinción entre los efectos secundarios y el valor de los cálculos, y el uso de "evaluación" (como lo hace la norma) de una expresión, para abarcar tanto. Creo que mi principal interrogatorio no es sólo acerca de si el comportamiento está definido o no en este ejemplo, pero, ¿cómo se debe leer la norma en orden a decidir esto. En particular, debe tomar la equivalencia de E op= F a E = E op F como máxima autoridad para la semántica del compuesto operación de asignación (en cuyo caso el ejemplo que claramente ha UB), o simplemente como una indicación de lo que la operación matemática que está involucrado en la determinación del valor asignado (es decir, el identificado por op, con el lvalue-a-r-value convertido en el lado izquierdo del operador de asignación compuesta como a la izquierda del operando y su RHS como operando derecho). La última opción hace que sea mucho más difícil argumentar a favor de la UB en este ejemplo, como he tratado de explicar. Tengo que admitir que es tentador hacer la equivalencia de autoridad (de manera que el compuesto de las asignaciones de convertirse en una especie de segunda clase primitivas, cuyo significado está dado por volver a escribir en el plazo de primera clase primitivas; por lo tanto la definición del lenguaje sería simplificado), pero no son bastante fuertes argumentos en contra de esto:

  • La equivalencia no es absoluta, debido a la "E se evalúa sólo una vez" la excepción. Tenga en cuenta que esta excepción es esencial para evitar la realización de cualquier uso en los que la evaluación de la E implica un efecto secundario de un comportamiento indefinido, por ejemplo, en el bastante común a[i++] += b; de uso. Si de hecho creo que no hay absolutamente equivalente de reescritura para eliminar el compuesto de asignaciones es posible; el uso de una ficticia ||| operador para designar unsequenced evaluaciones, uno podría intentar definir E op= F; (con int operandos por simplicidad) como equivalente a { int& L=E ||| int R=F; L = L + R; }, pero, a continuación, el ejemplo, ya no tiene la UB. En cualquier caso la norma no nos da ninguna rewriitng receta.

  • La norma no tratar compuesto de las tareas como de segunda clase primitivas para que no se separa la definición de la semántica es necesario. Por ejemplo, en 5.17 (el énfasis es mío)

    El operador de asignación (=) y el compuesto de asignación los operadores de todo el grupo de derecha a izquierda. [...] En todos los casos, la asignación es la secuencia después de que el valor cálculo de la derecha y la izquierda operandos, y antes de que el valor de cálculo de la expresión de asignación. Con respecto a un indeterminately en la secuencia de llamada de función, el funcionamiento de un compuesto de asignación es una sola evaluación.

  • Si la intención fuera a dejar compuesto asignaciones de ser meros shorthands para tareas sencillas, no habría razón para incluir de forma explícita en esta descripción. La frase final incluso se contradice directamente lo que sería el caso si la equivalencia fue tomada para ser autorizada.

Si uno admite que el compuesto asignaciones tienen una semántica propia, entonces el punto se plantea que la evaluación implica (aparte de la operación matemática) más que un efecto secundario (la tarea) y un valor de evaluación (secuenciado a partir de la asignación), pero también a un anónimo operación de recuperación de los (anterior) valor de la LHS. Esto normalmente se tratan bajo el título de "lvalue-a-r-value de conversión", pero lo hacemos aquí es difícil de justificar, ya que no hay ningún operador que toma el lado izquierdo como un r-value operando (aunque hay uno en el ampliado "equivalente"). Es precisamente esta sin nombre operación cuyo potencial unsequenced relación con el efecto secundario de ++ sería la causa de la UB, pero este unsequenced relación es la nada de forma explícita en la norma, porque la sin nombre operación no es. Es difícil justificar la UB mediante una operación cuya existencia es sólo implícita en la norma.

16voto

dyp Puntos 19641

Acerca de la descripción de i = ++i + 1

Tengo entendido que el sutil explicación es que

(1) la expresión ++i devuelve un lvalue pero + toma prvalues como operandos, por lo que una conversión de lvalue a prvalue debe ser llevada a cabo;

Probablemente, ver CWG problema activo de 1642.

esto implica la obtención de la valor actual de que lvalue (en lugar de uno más que el valor antiguo de i) y por lo tanto debe ser secuenciado después de que el efecto secundario de la incremento (es decir, la actualización de i)

La secuenciación de aquí se define por el incremento (indirectamente, a través de +=, ver (a)): El efecto secundario de la ++ (la modificación de i) es la secuencia antes de que el valor de cálculo de la expresión completa ++i. El último se refiere a la informática el resultado de ++i, no carga el valor de i.

(2) el lado izquierdo de la asignación también es una lvalue, por lo que su valor de evaluación no implica la obtención de la actual valor de i; mientras que este valor de cálculo es unsequenced w.r.t. el valor de cálculo de la RHS, esto no plantea ningún problema

No creo que esté bien definido en el Estándar, pero yo estaría de acuerdo.

(3) el valor cálculo de la tarea en sí consiste en la actualización i (de nuevo),

El valor de cálculo de i = expr sólo es necesario cuando se utiliza el resultado, por ejemplo int x = (i = expr); o (i = expr) = 42;. El valor de la computación en sí no modifica i.

La modificación de i en la expresión i = expr que ocurre debido a la = se llama el efecto secundario de =. Este efecto secundario es la secuencia antes de que el valor de cálculo de i = expr - o más bien el valor de cálculo de i = expr es la secuencia después de que el efecto secundario de la asignación en i = expr.

En general, el valor de cálculo de los operandos de una expresión se ordenan antes de que el efecto secundario de que la expresión, por supuesto.

pero es la secuencia después de que el valor de cálculo de su lado derecho, y por lo tanto, después de la actualización anterior a i; no hay problema.

El efecto secundario de la asignación i = expr es la secuencia después de que el valor de cálculo de los operandos i (A) y expr de la asignación.

El expr en este caso es un +-expresión: expr1 + 1. El valor de cálculo de esta expresión es la secuencia después de que el valor de los cálculos de sus operandos expr1 y 1.

El expr1 aquí ++i. El valor de cálculo de ++i es la secuencia después de que el efecto secundario de la ++i (la modificación de i) (B)

Por eso, i = ++i + 1 es seguro: Hay una cadena de secuenciado antes de que entre el valor de cálculo en (A) y los efectos secundarios en la misma variable en (B).


(a) El Estándar define ++expr en términos de expr += 1, que se define como expr = expr + 1 con expr se evalúa sólo una vez.

Para este expr = expr + 1, por lo tanto, solo tienen un valor de cálculo de expr. El efecto secundario de la = es la secuencia antes de que el valor de cálculo de la totalidad de la expr = expr + 1, y es secuenciado después de que el valor de cálculo de los operandos expr (LHS) y expr + 1 (RHS).

Esto corresponde a mi afirmación de que para ++expr, el efecto secundario es la secuencia antes de que el valor de cálculo de ++expr.


Acerca de la i += ++i + 1

¿El valor de cálculo de i += ++i + 1 implican un comportamiento indefinido?

Desde el En el lado izquierdo de la += es todavía un lvalue (y su RHS todavía un prvalue), el mismo razonamiento como el anterior se aplica en cuanto a (1) y (2) son de que se trate; como para (3) el valor de cálculo de la += operador ahora debe recuperar la valor actual de i, y, a continuación, (obviamente secuenciado después de que, incluso si la norma no lo dice explícitamente, o de lo contrario, la ejecución de este tipo de operadores siempre invocar un comportamiento indefinido) realizar el además de los RHS y almacenar el resultado en i.

Creo que aquí está el problema: La suma de un valor de i obtenido a partir de la LHS de i += el resultado de ++i + 1 requiere cargar el valor de i. Esta carga es unsequenced respecto de la modificación realizada por ++i. Esto es esencialmente lo que usted dice en su descripción alternativa, después de la reescritura, por mandato de la Norma i += expr -> i = i + expr. Aquí, el valor de cálculo de i es unsequenced wrt el valor de cálculo de expr. Que es donde usted consigue la UB.


Otro enfoque para i += ++i + 1

el valor de cálculo de la += operador ahora debe recuperar la valor actual de i, y , a continuación, [...] realizar la adición de los RHS

El RHS ser ++i + 1. Informática el resultado de esta expresión se puede hacer "simultáneamente" con cargar el valor de i desde el lado izquierdo. Así que la palabra , a continuación, en esta frase es engañosa: por supuesto, primero debe cargar i y, a continuación, añadir el resultado de la RHS. Pero el cálculo de la RHS se puede hacer "simultáneamente" con la carga de i, de modo que usted puede obtener ya sea el antiguo o el nuevo valor de i, según modificado por el lado derecho.

En general, una tienda y un "concurrentes" carga de datos de la carrera, lo que conduce a un Comportamiento Indefinido.

"al mismo tiempo" == unsequenced; unsequenced puede implicar que se realiza simultáneamente, por lo que podemos utilizar la concurrencia a mostrar uno de los resultados posibles


Abordar el anexo

el uso de un ficticio ||| operador para designar unsequenced evaluaciones, uno podría intentar definir E op= F; (con int operandos por simplicidad) como equivalente a { int& L=E ||| int R=F; L = L + R; }, pero, a continuación, el ejemplo, ya no tiene la UB.

Deje E ser i y F ser ++i (no es necesario que el + 1). Entonces, para i = ++i

int* lhs_address;
int lhs_value;
int* rhs_address;
int rhs_value;

    (         lhs_address = &i)
||| (i = i+1, rhs_address = &i, rhs_value = *rhs_address);

*lhs_address = rhs_value;

Por otro lado, para i += ++i

    (         lhs_address = &i, lhs_value = *lhs_address)
||| (i = i+1, rhs_address = &i, rhs_value = *rhs_address);

int total_value = lhs_value + rhs_value;
*lhs_address = total_value;

Esta es la intención de representar a mi comprensión de la secuenciación de garantías. Tenga en cuenta que el , operador de secuencias valor de todos los cálculos y los efectos secundarios de la LHS, antes que los de los RHS. Los paréntesis no afectan a la secuencia. En el segundo caso, i += ++i, tenemos una modificación de i unsequenced wrt un lvalue-a-r-value de conversión de i => UB.

La norma no tratar compuesto de las tareas como de segunda clase primitivas para que no se separa la definición de la semántica es necesario.

Yo diría que es una redundancia. La reescritura de E1 op = E2 a E1 = E1 op E2 también incluye que la expresión de los tipos y categorías de valor son necesarios (en el lado derecho, 5.17/1 le dice algo acerca de la lhs), ¿qué sucede con los tipos de puntero, las conversiones necesarias, etc. Lo triste es que la frase acerca de "Con respecto a un.." en 5.17/1 no está en 5.17/7 como una excepción de la equivalencia.

De cualquier manera, creo que debemos comparar las garantías y requisitos para la asignación compuesta vs asignación simple además el operario, y ver si hay alguna contradicción.

Una vez nos ponemos que "Con respecto a un..", también en la lista de excepciones en 5.17/7, no creo que hay una contradicción.

Resulta que, como se puede ver en la discusión de Marc van Leeuwen la respuesta, esta frase nos lleva a la siguiente observación interesante:

int i; // global
int& f() { return ++i; }
int main() {
    i  = i + f(); // (A)
    i +=     f(); // (B)
}

Parece que (A) tiene dos resultados posibles, desde la evaluación del cuerpo de f es indeterminately secuenciados con el valor de cálculo de la i en i + f().

En (B), por otro lado, la evaluación del cuerpo de f() es la secuencia antes de que el valor de cálculo de i, desde += debe considerarse como una sola operación, y f() sin duda debe ser evaluado antes de la asignación de +=.

5voto

Shafik Yaghmour Puntos 42198

La expresión:

i += ++i + 1

hace invocar un comportamiento indefinido. El lenguaje abogado método nos obliga a volver al informe de defecto que se traduce en:

i = ++i + 1 ;

convertirse en bien definido en C++11, que es el informe de defecto 637. Reglas de secuencia y el ejemplo está en desacuerdo , que empieza diciendo:

En 1.9 [intro.la ejecución] el párrafo 16, la siguiente expresión es todavía aparece como un ejemplo de un comportamiento indefinido:

i = ++i + 1;

Sin embargo, parece que las nuevas reglas de secuencia realizar esta expresión bien definido

La lógica que se utiliza en el informe es el siguiente:

  1. La asignación de efectos secundarios es necesario para ser secuenciados después de que el valor de los cálculos tanto de su LHS y RHS (5.17 [expr.culo] el párrafo 1).

  2. El LHS (i) es un lvalue, por lo que su valor de cálculo implica el cálculo de la dirección de i.

  3. En fin valor a calcular los RHS (++i + 1), es necesario en primer lugar el valor a calcular el lvalue expresión ++y, a continuación, hacer un lvalue-a-r-value de conversión en el resultado. Esto garantiza que el aumento de efectos secundarios es la secuencia antes de que el cálculo de la operación de adición, que a su vez se ordenan antes de la asignación de efectos secundarios. En otras palabras, produce un bien definido por el orden y el valor final de esta expresión.

Así que en esta cuestión el problema de los cambios de la RHS que va de:

++i + 1

a:

i + ++i + 1

debido a que el proyecto de C++11 estándar de la sección 5.17 de Asignación y los compuestos de los operadores de asignación que dice:

El comportamiento de una expresión de la forma E1 op = E2 es equivalente a E1 = E1 op E2, excepto que E1 se evalúa sólo una vez. [...]

Así que ahora tenemos una situación en la que el cálculo de i en la RHS no está secuenciado en relación a ++i y por lo que a continuación tienen un comportamiento indefinido. Esto se desprende de la sección 1.9 el párrafo 15 , que dice:

Excepto donde se indique lo contrario, las evaluaciones de los operandos de los operadores individuales y de las subexpresiones de expresiones individuales son unsequenced. [ Nota: En una expresión que se evalúa más de una vez durante el la ejecución de un programa, unsequenced y indeterminately secuenciado las evaluaciones de sus subexpresiones no tiene necesidad de realizarse constantemente en las diferentes evaluaciones. -fin de la nota ] El valor de los cálculos de la los operandos de un operador son secuenciados, antes de que el valor de cálculo de el resultado del operador. Si un efecto secundario de un escalar objeto es unsequenced en relación con cualquiera otro efecto secundario en el mismo escalar objeto o un valor de cálculo utilizando el valor de la misma escalar objeto, el comportamiento es indefinido.

La pragmática manera de mostrar esto sería el uso de clang a probar el código que genera la siguiente advertencia (ver en vivo):

warning: unsequenced modification and access to 'i' [-Wunsequenced]
i += ++i + 1 ;
  ~~ ^

para que este código:

int main()
{
    int i = 0 ;

    i += ++i + 1 ;
}

Esto se ve reforzado por este explícita ejemplo de prueba en clang's suite de prueba para -Wunsequenced:

 a += ++a; 

1voto

MWid Puntos 1373

Sí, es UB!

La evaluación de la expresión

i += ++i + 1

procede en los siguientes pasos:

5.17p1 (C++11) los estados (el énfasis es mío):

El operador de asignación (=) y el compuesto de asignación los operadores de todo el grupo de derecha a izquierda. Todos requieren un modificables lvalue como su izquierda el operando y devuelve un lvalue refiriéndose a la izquierda del operando. El resultado en todos los casos es un poco de campo si la izquierda el operando es un poco de campo. En todos los casos, la asignación es la secuencia después de que el valor de cálculo de la derecha y la izquierda operandos, y antes de que el valor de cálculo de la expresión de asignación.

¿Qué significa "valor de cálculo"?

1.9p12 da la respuesta:

El acceso a un objeto designado por un volátiles glvalue (3.10), la modificación de un objeto, llamar a una biblioteca de la función de e/S, o llamar a una función que realiza cualquiera de las operaciones que son todos los efectos secundarios, que son los cambios en el estado del entorno de ejecución. La evaluación de una expresión (o una sub-expresión) en general incluye tanto el valor de los cálculos (incluyendo la determinación de la identidad de un objeto para glvalue la evaluación y la obtención de un valor previamente asignado a un objeto para prvalue de evaluación) y la iniciación de efectos secundarios.

Dado que el código utiliza un operador de asignación compuesta, 5.17p7 nos dice, cómo este operador se comporta:

El comportamiento de una expresión de la forma E1 op= E2 es equivalente a E1 = E1 op E2 except thatE1 se evalúa sólo una vez.

Por lo tanto la evaluación de la expresión E1 ( == i) implica tanto, la determinación de la identidad del objeto designado por i y un lvalue-a-r-value de conversión para obtener el valor almacenado en el objeto. Pero la evaluación de los dos operandos E1 y E2 no están secuenciados con respecto a cada uno de los otros. De este modo obtenemos un comportamiento indefinido desde la evaluación de la E2 ( == ++i + 1) inicia un efecto secundario (actualización i).

1.9p15:

... Si un efecto secundario de un escalar objeto es unsequenced relativa a cualquiera otro efecto secundario en el mismo escalar objeto o un valor de cálculo utilizando el valor de la misma escalar el objeto, el comportamiento es indefinido.


Las siguientes declaraciones en su pregunta/comentario parece ser la raíz de su incomprensión:

(2) el lado izquierdo de la asignación también es un lvalue, por lo que su valor de evaluación no implica ir a buscar el valor actual de i

obtención de un valor puede ser parte de un prvalue de evaluación. Pero en E += F la única prvalue es F para buscar el valor de E no es parte de la evaluación de la (lvalue) subexpresión E

Si una expresión es un lvalue o r-value no dice nada acerca de cómo esta expresión se puede evaluar. Algunos operadores requieren lvalues como sus operandos algunos otros requieren rvalues.

Cláusula 5p8:

Cada vez que un glvalue expresión aparece como un operando de un operador que espera un prvalue para que operando, el lvalue-a-r-value (4.1), la matriz-a-puntero (4.2), o la función-a-puntero (4.3) estándar que se apliquen las conversiones para convertir la expresión en una prvalue.

En una simple asignación de la evaluación de la LHS, sólo requiere de la determinación de la identidad del objeto. Pero en un recinto de trabajo, como por ejemplo += el lado izquierdo debe ser modificable lvalue, pero la evaluación de la LHS en este caso consiste en determinar la identidad del objeto, y un lvalue-a-r-value de la conversión. Es el resultado de esta conversión (que es un prvalue) que se añade al resultado (también un prvalue) de la evaluación de los RHS.

"Pero en E += F la única prvalue es F para buscar el valor de E no es parte de la evaluación de la (lvalue) subexpresión E"

Eso no es cierto, como he explicado anteriormente. En tu ejemplo F es un prvalue expresión, pero F puede ser un lvalue expresión. En ese caso, el lvalue-a-r-value de conversión se aplica también a F. 5.17p7 como se citó anteriormente nos dice, lo que la semántica del compuesto operadores de asignación son. La norma establece que el comportamiento de E += F es el mismo de E = E + F pero E sólo se evalúa una vez. Aquí, la evaluación de la E incluye el lvalue-a-r-value de la conversión, porque el operador binario + requiere operandos a ser rvalues.

0voto

gnasher729 Puntos 5011

Desde el compilador del escritor perspectiva, no les importa acerca de "i += ++i + 1", porque cualquiera que sea el compilador hace, el programador no puede obtener el resultado correcto, pero que seguramente reciben lo que se merecen. Y nadie escribe código como ese. Lo que el compilador de escritor que le interesa es el

*p += ++(*q) + 1;

El código debe leer *p y *q, aumentar *q por 1, y de aumento *p por cierta cantidad que se calcula. Aquí el compilador escritor se preocupa acerca de las restricciones en el orden de las operaciones de lectura y escritura. Obviamente, si p y q apuntan a diferentes objetos, el orden no hace ninguna diferencia, pero si p == q, a continuación, se hará una diferencia. De nuevo, p será diferente a partir de q, a menos que el programador escribe el código es una locura.

Por lo que el código no definido, el lenguaje permite que el compilador para producir la forma más rápida posible código sin importarle loco programadores. Por lo que el código es definido, el lenguaje obliga al compilador a producir código que se ajusta a la norma, incluso en la locura de los casos, lo que puede hacer que funcione más lento. Tanto el compilador de escritores y programadores en su sano juicio no le gusta eso.

Así que incluso si el comportamiento está definido en C++11, sería muy peligrosa para su uso, debido a que (a) un compilador podría no ser cambiado de C++03 comportamiento, y (b) podría ser un comportamiento indefinido en C++14, por las razones arriba.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X