Antes de empezar a gritar un comportamiento indefinido, esto es explícitamente mencionados en N4659 (C++17)

  i = i++ + 1;        //the value of i is incremented

Sin embargo, en N3337 (C++11)

  i = i++ + 1;        //the behavior is undefined

Lo que ha cambiado?

De lo que he entendido, a partir de [N4659 básica.exec]

Excepto donde se indique lo contrario, las evaluaciones de los operandos de los operadores individuales y de las subexpresiones de expresiones individuales son unsequenced. […] El valor de los cálculos de los operandos de un operador son secuenciados, antes de que el valor de cálculo del resultado del operador. Si un efecto secundario en una ubicación de memoria es unsequenced en relación con cualquiera otro efecto en la misma ubicación de memoria o un valor de cálculo utilizando el valor de cualquier objeto en la misma posición de memoria, y que no son potencialmente concurrentes, el comportamiento es indefinido.

Donde valor se define en [N4659 básica.tipo]

Para trivialmente copiable tipos, el valor de la representación es un conjunto de bits en la representación del objeto que determina un valor, que es un elemento discreto de la implementación de un conjunto definido de valores

De [N3337 básica.exec]

Excepto donde se indique lo contrario, las evaluaciones de los operandos de los operadores individuales y de las subexpresiones de expresiones individuales son unsequenced. […] El valor de los cálculos de los operandos de un operador son secuenciados, antes de que el valor de cálculo del 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 el objeto, el comportamiento es indefinido.

Asimismo, el valor se define en [N3337 básica.tipo]

Para trivialmente copiable tipos, el valor de la representación es un conjunto de bits en la representación del objeto que determina un valor, que es un elemento discreto de la implementación de un conjunto definido de valores.

Son idénticos a excepción de la mención de la concurrencia que no importa, y con el uso de ubicación de memoria en lugar de escalar objeto, donde

Aritmética de los tipos de, tipos de enumeración, los tipos de puntero, puntero a miembro tipos, std::nullptr_t, y la cv-versiones completas de estos tipos se denominan tipos escalares.

Que no afecta el ejemplo.

De [N4659 expr.culo]

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. El operando derecho es la secuencia antes de que la izquierda operando.

De [N3337 expr.culo]

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.

La única diferencia de la última frase está ausente en N3337.

La última frase, sin embargo, no debería tener ninguna importancia como el de la izquierda el operando i no es ni «otro efecto secundario» ni «utilizando el valor de la misma escalar el objeto» como el id-expresión es un lvalue.

  • Se han identificado la razón: En C++17, el operando derecho es la secuencia antes de que la izquierda del operando. En C++11 no hubo tal secuenciación. Lo que, precisamente, es su pregunta?
  • Véase la última frase.
  • Hay dos unsequenced efectos secundarios, tanto escribe: i = tiene el efecto secundario de la escritura a i. i++ tiene el efecto secundario de la escritura a i.
  • Si están unsequenced, que sería el ejemplo de la norma de forma totalmente equivocada de que iba a ser muy reticentes a afirmar que son unsequenced.
  • Son unsequenced en C++11, secuenciados en C++17. De ahí las diferentes ejemplos.
  • ¿Alguien tiene un enlace a la motivación para este cambio? Me gustaría que un analizador estático de ser capaz de decir «no quiero hacer eso» cuando se enfrentan con un código como i = i++ + 1;.
  • es de papel de la p0145r3.pdf: «Perfeccionando la Evaluación de la Expresión de la Orden para Idiomáticas C++».
  • Que poco de eso, exactamente – yo no podía ver nada relevante, pero tal vez ese es mi mal.
  • En una mirada superficial de que el papel, la sección 5 implica que no cubre el caso de ++.
  • la sección número 2 dice que esto es contra intuitivo, incluso los expertos no hacer lo correcto en todos los casos. Eso es casi todo de su motivación.
  • el documento agrega que en el texto sobre el operador de asignación. No funciona en unario expresiones en todos los contextos, sino que afectan a cómo se procesan como parte de la asignación como la aceptada respuesta explica.
  • Yo diría que cualquier compilador que no puede leer que está libre de errores.
  • N4700 es una más actual proyecto de C++17.
  • Entonces, ¿qué sucede cuando usted vaya a «c=c++ de +1;»? Es la misma cosa como la «c=c+2»?
  • que sería c=c+1, ya tha valor producido por c++ de + 1, c+1.
  • El OP de la primera línea de código menciona lo que hace. Aunque en mi humilde opinión, el comentario debería haber dicho «obtiene 1 agrega que» en lugar de «se incrementa».
  • Preguntas como estas son la razón de esta propuesta existe: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0431r0.htm, pero por supuesto que no era defendido adecuadamente en ninguna de las reuniones del comité y, probablemente, sólo murió una muerte lenta.

InformationsquelleAutor Passer By | 2017-12-07

3 Comentarios

  1. 136

    En C++11 de la ley de «asignación», es decir, el efecto de la modificación de la LHS, es la secuencia después de la valor de cálculo de del operando derecho. Tenga en cuenta que este es un relativamente débil » garantía: se produce la secuenciación sólo en relación con los valor de cálculo de de los RHS. No dice nada sobre la efectos secundarios que pueden estar presentes en el lado derecho, desde la aparición de efectos secundarios no es parte de valor de cálculo de. Los requisitos de C++11 establecer ninguna relación de secuenciación entre el acto de asignación y efectos secundarios de los RHS. Esto es lo que crea el potencial para la UB.

    La única esperanza en este caso es de garantías adicionales realizadas por los operadores utilizados en la RHS. Si el lado derecho se utiliza un prefijo ++, la secuenciación de las propiedades específicas en la forma de prefijo de ++ habría ahorrado el día en este ejemplo. Pero postfix ++ es una historia diferente: no hacer este tipo de garantías. En C++11 los efectos secundarios de = y postfix ++ terminan unsequenced con relación a cada uno de los otros en este ejemplo. Y que es UB.

    En C++17 un extra de sentencia se agrega a la especificación de operador de asignación:

    El operando derecho es la secuencia antes de que la izquierda operando.

    En combinación con lo anterior, se hace muy fuerte garantía. Que las secuencias de todo que sucede en el lado derecho (incluyendo los posibles efectos secundarios) antes de todo que sucede en el lado izquierdo. Dado que la asignación real es la secuencia de después de LHS (y RHS), que extra secuenciación aísla completamente el acto de asignación de efectos secundarios presentes en la RHS. Esta más fuerte de la secuenciación es lo que elimina la anterior UB.

    (Actualizado a tener en cuenta de @John Bollinger comentarios.)

    • Es realmente correcto para incluir «el acto de asignación» en los efectos cubierto por «la mano izquierda el operando» en este fragmento? La norma ha separado idioma acerca de la secuenciación de la tarea en sí. Debo tomar el extracto que usted ha presentado para ser de alcance limitado a la secuenciación de la mano izquierda y la mano derecha de las sub-expresiones, que no parecen ser suficientes, en combinación con el resto de la sección, para apoyar a definedness de la OP de la instrucción.
    • Corrección: la asignación real todavía está secuenciado después de que el valor de cálculo de la izquierda el operando, y la evaluación de la izquierda el operando es la secuencia de después de (completar) evaluación del operando derecho, de modo que sí, que el cambio es suficiente para apoyar el bien definedness el OP le preguntó acerca de. Sólo estoy discutiendo los detalles, entonces, pero no importa, ya que podría tener consecuencias diferentes de códigos diferentes.
    • Yo creo que garantizan la secuencia de la izquierda lvalue la evaluación podría perjudicar innecesariamente la eficiencia de cada simple de generación de código. Sencillo de evaluación de *foo() = globalVariable;, por ejemplo, invocar foo(), a continuación, recuperar globalVariable y guardar en el puntero recibido de foo(). Capturar el valor de globalVariable antes de la llamada si el código no es necesario habría que agregar el costo; si el código no necesita la variable antes de la llamada, la copia a una variable temporal no debe agregar ningún costo en comparación con el mandato de la semántica.
    • No estoy en desacuerdo, @supercat, pero la norma dice lo que dice, y la conformación de los programas deben cumplir. Que en un «como si» de sentido, sin embargo: un programa que tiene una gran cantidad de internos libertad de acción, siempre que su comportamiento externo es exactamente como si es operado estrictamente como especifica el estándar. Con respecto a tu ejemplo en particular, la captura es de curso si el compilador puede demostrar que foo() no modificar globalVariable. A menos que pueda hacerlo, es necesario hacer que la copia temporal antes de realizar la llamada a la función.
    • Me parece curioso que los autores de la Norma de hacer un cambio que afecta a la eficiencia de incluso sencillo de generación de código y que históricamente no ha sido necesario, y, sin embargo, se resisten a la definición de otros comportamientos cuya ausencia es un problema mucho más grande, y que rara vez suponen ningún impedimento significativo para la eficiencia.
    • Estoy ahí contigo, @supercat. Supongo que el comité del estándar quería aclarar esta fuente de perpetua nuevo-programador sorpresa, pero eso es bastante débil razón, y no estoy en absoluto convencido de que el beneficio justifica el costo. Por otra parte, otros similares undefinedness la permanencia de los problemas, tales como la evaluación de i++ + i.
    • Lo que es particularmente extraño aquí es que esto podría, en algunos casos, representan un cambio de hora, ya que algunas implementaciones, legítimamente, tienen garantizado el comportamiento contrario a la nueva Norma. No me importa mucho casos como i++ + i (ni i=i++ porque un programador que quiere ningún orden particular puede exigir fácilmente uno. Mucho más problemático es la incapacidad para bloquear el compilador de reordenación de trabajadores no cualificados objeto de accesos a través de ciertas operaciones-indica al compilador que en una secuencia como foo(); barrier(); boz();, el compilador no puede reordenar las operaciones…
    • …de foo en boz, ni viceversa, incluso si esas operaciones parece para operar en «normal» (sin calificar) de los objetos. En muchos casos, no es práctico el uso de calificadores de objetos de acceso a través de código externo, que puede ser cambiado en las formas en que el compilador no sabe acerca de a veces que el código no se ejecuta. Tenga en cuenta que el trato con el hardware de la memoria de la reordenación sería responsabilidad del programador, pero un programador no puede lidiar con el compilador de reordenación sin herramientas para su control.

  2. 33

    Identificó la nueva sentencia

    El operando derecho es la secuencia antes de que la izquierda operando.

    y usted identificó correctamente que la evaluación de la izquierda el operando como un lvalue es irrelevante. Sin embargo, secuenciado antes de se especifica a una relación transitiva. La completa operando derecho (incluyendo el post-incremento) por lo tanto es también secuenciado antes de la asignación. En C++11, sólo el valor de cálculo de del operando derecho fue secuenciado antes de la asignación.

  3. 7

    En los mayores estándares de C++ y en C11, la definición del operador de asignación texto termina con el texto:

    Las evaluaciones de los operandos son unsequenced.

    Lo que significa que efectos secundarios de los operandos son unsequenced y por lo tanto sin duda un comportamiento indefinido si usan la misma variable.

    Este texto fue extirpado en C++11, dejando un poco ambiguo. Es UB o no? Esto ha sido aclarado en C++17 donde se les ha añadido:

    El operando derecho es la secuencia antes de que la izquierda operando.


    Como una nota de lado, incluso en los mayores estándares, esto fue todo muy claro, por ejemplo, de C99:

    El orden de evaluación de los operandos es indeterminado. Si se realiza un intento de modificar
    el resultado de un operador de asignación o para acceder a él después de la secuencia siguiente punto, el
    el comportamiento es indefinido.

    Básicamente, en C11/C++11, se desordenó cuando partieron de este texto.

Dejar respuesta

Please enter your comment!
Please enter your name here