Estaba leyendo acerca de orden de evaluación de las violaciones, y dan un ejemplo que me intriga.

1) Si un efecto secundario de un escalar objeto de la onu-secuenciado en relación a otro efecto secundario en el mismo escalar el objeto, el comportamiento es indefinido.

//snip
f(i = -1, i = -1); //undefined behavior

En este contexto, i es un escalar objeto, que aparentemente significa

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

No veo la manera de que la afirmación es ambigua en ese caso. A mí me parece que, independientemente de si el primer o el segundo argumento se evalúa en primer lugar, i termina como -1, y ambos argumentos son también -1.

Por favor alguien puede aclarar?


ACTUALIZACIÓN

Realmente aprecio toda la discusión. Hasta ahora, me gusta @harmínico la respuesta mucho ya que expone a los riesgos y la complejidad de la definición de esta declaración, a pesar de lo recto hacia adelante se ve a primera vista. @acheong87 señala algunos problemas que surgen al utilizar referencias, pero creo que es ortogonal a la unsequenced efectos secundarios de los aspectos de esta cuestión.


RESUMEN

Esta cuestión ya tengo un montón de atención, voy a resumir los puntos principales y respuestas. En primer lugar, permítanme una pequeña digresión para señalar que «por qué» puede tener estrechamente relacionados, sin embargo, sutilmente diferentes significados, a saber, «por lo que causa«, «para qué razón«, y «para lo que propósito«. Me va a agrupar las respuestas por cuál de los significados de «por qué» se abordaron.

por qué causa

La principal respuesta de aquí proviene de Paul Draper, con Martin J contribuyendo con una similar, pero no tan extensa respuesta. Paul Draper, la respuesta se reduce a

Es un comportamiento indefinido porque no está definido cuál es el comportamiento.

La respuesta es en general muy bueno en términos de explicar lo que el estándar de C++, dice. También aborda algunos de los casos de la UB como f(++i, ++i); y f(i=1, i=-1);. En el primero de los casos, no está claro si el primer argumento debe ser i+1 y la segunda i+2 o viceversa; en el segundo, no está claro si i debe ser 1 o -1 después de la llamada a la función. Ambos de estos casos son de la UB, porque caen bajo la siguiente regla:

Si un efecto secundario de un escalar objeto es unsequenced en relación a otro efecto secundario en el mismo escalar el objeto, el comportamiento es indefinido.

Por lo tanto, f(i=-1, i=-1) es también de la UB, ya que cae bajo la misma regla, a pesar de que la intención del programador es (en mi humilde opinión) obvio e inequívoco.

Paul Draper también se hace explícita en su conclusión de que

Podría haber sido definido de comportamiento? Sí. Fue definido? No.

que nos lleva a la pregunta de «¿por qué motivo/finalidad era f(i=-1, i=-1) a la izquierda como un comportamiento indefinido?»

¿por qué motivo /finalidad

Aunque hay algunos descuidos (tal vez por descuido) en el estándar de C++, muchas omisiones son bien razonado y servir a un propósito específico. Aunque soy consciente de que el objetivo a menudo es «hacer que el compilador del escritor trabajo más fácil», o «código más rápido», estaba principalmente interesado en saber si hay una buena razón para dejar f(i=-1, i=-1) como UB.

harmínico y supercat las principales respuestas que proporcionan un razón por la UB. Harmínico señala que un compilador de optimización que se puede romper hasta el ostensiblemente atómica de asignación de operaciones en múltiples instrucciones de la máquina, y que además podría intercalar esas instrucciones para una velocidad óptima. Esto podría llevar a algunos resultados muy sorprendentes: i termina como el -2 en su escenario! Por lo tanto, harmínico muestra cómo asignar el mismo valor a una variable más de una vez puede tener efectos negativos si las operaciones son unsequenced.

supercat proporciona una relacionados con la exposición de los riesgos de tratar de obtener f(i=-1, i=-1) a hacer lo que parece que debemos hacer. Señala que en algunas arquitecturas, hay duras restricciones en contra de múltiples simultáneas escribe a la misma dirección de memoria. Un compilador podría tener un tiempo difícil detectar este si que estamos tratando con algo menos trivial de lo que f(i=-1, i=-1).

davidf también proporciona un ejemplo de intercalado de instrucciones muy similar a harmínico del.

Aunque cada uno de harmínico s, supercat y davidf’ ejemplos son algo artificial, tomados en conjunto que todavía sirven para proporcionar un tangibles razón por la que f(i=-1, i=-1) debe ser un comportamiento indefinido.

Acepté harmínico la respuesta de porque se hizo el mejor trabajo de abordar todos los significados de por qué, a pesar de que Pablo Draper de la respuesta al «por qué causa» parte mejor.

otras respuestas

JohnB señala que si consideramos que la sobrecarga de operadores de asignación (en lugar de simplemente escalares), entonces podemos ejecutar en problemas así.

  • No creo que se supone que iba a ser un operador coma, pero en lugar de dos parámetros a una función.
  • La coma , entre los argumentos de la función no es un operador coma. Consulte aquí.
  • Oh I miss interpretado pensé que if() es un f() función.
  • No sé por qué, pero tenía el mismo pensamiento en el primer vistazo. Alguna extraña respuesta psicológica para tratar de hacer sentido de ella, supongo 🙂
  • Un escalar objeto es un objeto de tipo escalar. Ver 3.9/9: «la Aritmética de los tipos (3.9.1), los tipos de enumeración, los tipos de puntero, puntero a miembro tipos (3.9.2), std::nullptr_t, y la cv-versiones completas de estos tipos (3.9.3) se denominan tipos escalares.»
  • Creo que rápidamente me comentó sobre la base de la pregunta del título. y `f( era como if en una mirada superficial a mí. Debo leer la pregunta.
  • Tal vez hay un error en la página, y que en realidad significaba f(i-1, i = -1) o algo similar.
  • Echa un vistazo a esta pregunta: stackoverflow.com/a/4177063/71074
  • Gracias. ¿La «aritmética de los tipos de» incluir bool?
  • Bueno, supongo que la respuesta es simple: el orden de evaluación de los parámetros dependerá de la convención de llamada utilizada por el compilador/arquitectura – que el programador no tiene mucho control(?!) y por lo que el comportamiento puede ser diferente de un compilador/arquitectura a otra.
  • SchighSchagh su actualización debería estar en la sección de respuestas.
  • Probablemente debería mencionar en su resumen que debido a que C++17 esto no es UB más, consulte @AlexDs respuesta

InformationsquelleAutor Nicu Stiurca | 2014-02-10

11 Comentarios

  1. 341

    Ya que las operaciones son unsequenced, no hay nada que decir que las instrucciones para la realización de la tarea no puede ser intercalada. Podría ser óptimo para hacerlo, dependiendo de la arquitectura de la CPU. La referencia de la página de los estados esto:

    Si no está secuenciado antes de B y B no es secuenciado antes de A, entonces
    existen dos posibilidades:

    • evaluaciones de a y B son unsequenced: pueden realizarse en cualquier orden y pueden superponerse (dentro de un único hilo de ejecución, la
      compilador puede intercalar las instrucciones de CPU que forman a y B)

    • evaluaciones de a y B son indeterminately la secuencia: se puede realizar en cualquier orden, pero no se superponen: Una será completa
      antes que B, o de B va a ser completa antes de que A. El orden puede ser el
      frente a la próxima vez que el mismo se evalúa la expresión.

    Que por sí mismo no parece que podrían causar un problema, suponiendo que la operación que se realiza es almacenar el valor -1 en una ubicación de memoria. Pero tampoco hay nada que decir que el compilador no puede optimizar de que en un conjunto de instrucciones que tiene el mismo efecto, pero que podría fallar si la operación fue intercalada con otra operación en la misma ubicación de memoria.

    Por ejemplo, imagine que era más eficiente a cero de la memoria, a continuación, disminuir, en comparación con la carga de un valor de -1 de. Entonces este:

    f(i=-1, i=-1)

    podría ser:

    clear i
    clear i
    decr i
    decr i

    Ahora me es de -2.

    Probablemente es un falso ejemplo, pero es posible.

    • Muy buen ejemplo de cómo la expresión de la realidad podía hacer algo inesperado mientras que conforme a las reglas de secuencia. Sí, un poco artificial, pero así es el código cortada estoy preguntando sobre en el primer lugar. 🙂
    • E incluso si la asignación se realiza como una operación atómica, es posible concebir una superscalar arquitectura, donde ambas tareas se realizan de forma simultánea causando que la memoria de acceso conflicto que resulta en un fracaso. El lenguaje está diseñado para que los programadores de compiladores tienen tanta libertad como sea posible en el uso de las ventajas de la máquina de destino.
    • Me gusta mucho tu ejemplo de cómo incluso asignar el mismo valor de la misma variable en ambos parámetros, se podría producir un resultado inesperado, ya que los dos asignaciones se unsequenced
    • +1e+6 (ok, +1) para el punto que el código compilado no es siempre lo que usted esperaría. Optimizadores son realmente buena en lanzar este tipo de curvas en usted cuando usted no sigue las reglas 😛
    • En el procesador Arm de una carga de 32 bits puede tomar hasta 4 instrucciones: no load 8bit immediate and shift hasta 4 veces. Normalmente el compilador hará direccionamiento indirecto para la obtención de un número de la forma de una mesa para evitar esto. (-1 se puede hacer en 1 la instrucción, pero otro ejemplo podría ser el elegido).
    • En algo así como un PIC 16Cxx, unsigned char foo=255; podría ser movlw 255 / movwf _foo o clrf _foo / decf foo. El primero se carga el registro de trabajo con 255; el último déjalo reposar.
    • Creo que esta respuesta hace más daño que bien. No importa si o no usted puede pensar que de alguna manera podría fallar. La norma dice que es indefinido y el estándar es la fuente autorizada de lo que hace o no la define. Período.
    • Buen ejemplo! El ruido se compilador pillado a esta situación con -Wunsequenced (supongo GCC lo haría así).

  2. 208

    Primera, «escalar el objeto» se refiere a un tipo como un int, float, o un puntero (ver ¿Qué es un escalar Objetos en C++?).


    Segundo, puede parecer más evidente que

    f(++i, ++i);

    tendría un comportamiento indefinido. Pero

    f(i = -1, i = -1);

    es menos evidente.

    Un poco diferente ejemplo:

    int i;
    f(i = 1, i = -1);
    std::cout << i << "\n";

    Lo que la asignación sucedido «último», i = 1, o i = -1? No es definido en la norma. Realmente, eso significa que i podría ser 5 (ver harmínico la respuesta completa explicación plausible de cómo se podría hacer a ser el caso). O que programa podría violación de segmento. O volver a formatear la unidad de disco duro.

    Pero ahora te preguntarás: «¿Qué pasa con mi ejemplo? He utilizado el mismo valor (-1) para ambas tareas. ¿Qué podría ser claro acerca de eso?»

    Estás en lo correcto…excepto en la forma en que el comité de estándares de C++ se describe este.

    Si un efecto secundario de un escalar objeto es unsequenced en relación a otro efecto secundario en el mismo escalar el objeto, el comportamiento es indefinido.

    Que podría han hecho una excepción especial para su caso especial, pero no lo hicieron. (Y por qué debería? ¿De qué serviría que nunca posiblemente?) Así, i todavía podría ser 5. O la unidad de disco duro puede estar vacío. Por lo tanto la respuesta a tu pregunta es:

    Es un comportamiento indefinido porque no está definido cuál es el comportamiento.

    (Esto merece énfasis debido a que muchos programadores piensan «undefined» significa «al azar», o «impredecible». No, significa no definido por el estándar. El comportamiento puede ser 100% coherente, y todavía ser indefinido.)

    Podría haber sido definido de comportamiento? Sí. Fue definido? No. Por lo tanto, es «indefinido».

    Que dijo: «no definido» no significa que el compilador va a formatear el disco duro…lo que significa que es podría y no por ello deja de ser compatible con los estándares de compilador. De manera realista, estoy seguro de que g++, Clang, y MSVC todos hacer lo que usted espera. Ellos simplemente no «tener».


    Una pregunta diferente podría ser ¿por Qué los estándares de C++ comité decide hacer este efecto secundario unsequenced?. Que respuesta va a implicar la historia y las opiniones de la comisión. O Lo que es bueno acerca de tener este efecto secundario unsequenced en C++?, que permite la justificación alguna, si no fue el razonamiento de la comisión de normas. Usted podría pedir a esas preguntas aquí, o en programmers.stackexchange.com.

    • «Siendo realistas, estoy seguro de que g++, Clang, y MSVC todos hacer lo que te espera.» – Siempre y cuando usted no pase ninguna de las opciones específicamente para la captura de un código como este, de todos modos.
    • sí, de hecho yo sé que si habilita -Wsequence-point para g++, se lo advertirá.
    • ¿Por qué esto es menos obvio? Va a cambiar el mismo parámetro antes de la secuencia de punto
    • En realidad estaba pensando más en algo como el sonido metálico del -fsanitize=undefined que, incluso si no puede detectar esta en su forma actual (no estoy seguro), podría en el futuro.
    • Es menos evidente porque se están cambiando a el mismo valor en ambas operaciones, y ni la asignación se basa en el valor anterior, por lo que el orden indefinido de operaciones parece irrelevante para el lector casual.
    • «Estoy seguro de que g++, Clang, y MSVC todos hacer lo que usted espera que» no volvería a confiar en un moderno compilador. Ellos son malos. Por ejemplo, se podría reconocer que este es un comportamiento indefinido y asumir este código es inalcanzable. Si no lo hacen, pueden hacerlo mañana. Cualquier UB es una bomba de tiempo.
    • harmic siempre un gran ejemplo de por QUÉ este UB pueden generarse efectos de la enfermedad. Los compiladores modernos, realmente a veces tales optimizaciones. No en la CPU? Mover a Itanium. O AVR. O, De Atari. (permítanme añadir que me los de la plataforma ejemplos son retóricas y no verificada 😉 )
    • Tradicionalmente, uno a veces puede señalar que el compilador está en la libertad de reaccionar a indefinido construcciones haciendo que los demonios de la mosca de la nariz. Por tanto, cualquiera que sea el comportamiento de un compilador hace en la cara de un indefinido de construcción es un nasal demonio.
    • Tengo curiosidad – ¿cómo se puede justificar diciendo que «…i podría ser el 5.» en el contexto del código en discusión? Voy de acuerdo en que, dada f(i = 1, i = -1) la variable i podría ser 1 o -1, pero no veo cómo i se le podría asignar un valor de 5 dado el código, como se muestra.
    • porque es un comportamiento indefinido. Así que si algún compilador decide que quiere conjunto de i a 5, o hacer cualquier otra cosa, entonces es todavía compatible con el compilador. No creo que en realidad significaba que i==5 sería el resultado probable.
    • la respuesta es mala, porque no es bueno» no es información muy útil, ¿no?
    • el comportamiento indefinido es el orden de evaluación de las asignaciones. Si usted dice «asignar 1», seguido por «asignar -1» y el compilador me a 5, que no es «un comportamiento indefinido» – que está mal.
    • eche un vistazo a Harmínico la respuesta y nos dicen por qué (o cualquier otro número resultante) no ser conformes a las normas.
    • como Harmínico, dijo, este es un falso ejemplo. Si el compilador genera código que no asigna un valor correcto, está mal. Podría tener una opción de que valor a asignar en que de la orden, pero no se puede asignar un valor incorrecto. En este ejemplo, suponiendo que las instrucciones para limpiar y reducir el valor de i se intercalan; esto no es una conducta razonable en la parte del compilador. Podría generar clear; decr; clear; decr, pero clear; clear; decr; decr no conduciría a nada de lo que el código necesario, y la única cosa generado debe ser un informe de error.
    • No estoy diciendo que este tipo de comportamiento sería razonable, pero yo no creo que sea explícitamente definidos por la norma – y como otros han mencionado, una vez que tienes la UB, cualquier cosa es permitida, incluyendo nasal demonios y el formato de las unidades de disco duro.
    • estás equivocado al asumir la serialización de las operaciones que se ejecutan en los datos. Incluso si la suposición es válida para la corriente de la corriente principal arquitecturas de hardware, un montón de otras arquitecturas han existido y van a ser desarrollados en el futuro.
    • Incluso puede ser más razonable de lo que usted piensa. Cuando usted está sentado el lujo de hacer algo de la UB, se puede utilizar para optimizar. Al hacer optimizaciones mientras no preocuparse acerca de cómo un caso de uso funciona, usted puede obtener divertida resultados.
    • La compila tiene absolutamente ninguna obligación de generar, incluso de forma remota de código correcto en la cara de un comportamiento indefinido. TIt puede incluso suponer que este código nunca es llamado y así sustituir a todo un nop (tenga en cuenta que los compiladores de hacer realidad tales supuestos en la cara de la UB). Por lo tanto, yo diría que la correcta reacción a un informe de error sólo puede ser «cerrado, funciona como la intención de»
    • De hecho, incluso he visto una guía de optimización del específicamente asesorar a la escritura de código que puede tener un comportamiento indefinido para permitir que las optimizaciones del compilador. (el ejemplo fue el uso de firmado bucle de variables del índice, por lo que el compilador no tiene que preocuparse acerca de la producción de corregir el comportamiento de un unsigned variable de índice de bucle desborde)
    • It is undefined behavior because it is not defined what the behavior is. Para mí, este tautológica respuesta es bastante insatisfactorio. Personalmente, tiendo a asociar la UB con las cosas que naturalmente no tienen sentido o de lo contrario son problemáticos para definir. Agradezco la discusión aquí, pero yo prefiero harmínico la respuesta que hace un mejor trabajo de la exposición de los riesgos de tratar de definir este. (Su respuesta casi sugerencias de que la especificación del comité era demasiado perezoso.)
    • la «razón» es esta: en el caso general, el comportamiento es incierto, ya que no hay ninguna relación «orden» para el parámetro de la función de evaluación. En tu caso concreto, no existe ninguna razón para hacerlo, así que no te preocupes por hacer una excepción.
    • supongo que usted no obtener el tautológica de la ironía.
    • Tal vez la particular arquitectura de la CPU requiere ajuste de valor del bit 4 antes del establecimiento de la 1, luego la desconexión de 4. Resultante en el valor 5. Yo sé, loco, pero cosas como que a veces sucede.
    • A veces, una reformulación de los términos (que sólo en la superficie parece ser una respuesta tautológica) es lo que la gente necesita. La mayoría de la gente nueva a las especificaciones técnicas pensar undefined behavior significa something random will happen, que está lejos de ser el caso la mayoría del tiempo.
    • Yo tiendo a asociar la UB con las cosas que naturalmente no tienen sentido o de lo contrario son problemáticos para definir», ese es el error que esta respuesta está tratando de corregir. No se debe asociar un comportamiento indefinido con cosas que son problemáticos para definir, se debe asociar con cosas que en realidad no están definidos. Es bueno si usted puede demostrar un ejemplo de por qué beneficia a algunos hipotética aplicación para que sea indefinido (como harmínico de la respuesta), pero todo lo que sea necesario para aclarar su declaración «i termina como -1» es que no está garantizado 😉
    • Es tan informativo como It is undefined behavior because it is not defined what the behavior is..
    • No me pregunte la pregunta. Acabo de responder. (Y que es la respuesta correcta.)
    • Correcto, sí, en el sentido de que x == x es correcta. Correcto no significa útil. Si yo le preguntara a ¿por Qué no X el caso?, de que nadie pudiera responder X no es el caso, ya que no es el caso que X., y que podría ser igual de correcto. Pero tal tautologías no son útiles. En cualquier caso, su reciente aclaración se ha eliminado la tautología de la tautología, por así decirlo. Tiene un upvote.
    • Una cita que es muy aplicable en este caso. «El peor resultado posible de un comportamiento indefinido es de hacer lo que estaban esperando.»

  3. 27

    Una razón práctica para no hacer una excepción de las reglas, tan solo porque los dos valores son iguales:

    //config.h
    #define VALUEA  1
    
    //defaults.h
    #define VALUEB  1
    
    //prog.cpp
    f(i = VALUEA, i = VALUEB);

    Considerar el caso de que esto estaba permitido.

    Ahora, algunos meses más tarde, surge la necesidad de cambiar

     #define VALUEB 2

    Aparentemente inofensivo, ¿no? Y, sin embargo, de repente prog.cpp no compilar.
    Sin embargo, creemos que la compilación no debe depender del valor de un literal.

    Línea de base: no hay excepción a la regla porque podría hacer una compilación exitosa depende del valor (en lugar del tipo) de una constante.

    EDITAR

    @HeartWare señaló que la constante de expresiones de la forma A DIV B no están permitidos en algunos idiomas, cuando B es 0, y la causa de la compilación a fallar. Por lo tanto el cambio de una constante podría causar errores de compilación en algún otro lugar. Que es, en mi humilde opinión, lamentable. Pero sin duda es buena para restringir tales cosas a lo inevitable.

    • Seguro, pero el ejemplo no el uso de los literales de tipo entero. Su f(i = VALUEA, i = VALUEB); tiene sin duda el potencial para un comportamiento indefinido. Espero que no son realmente de codificación en contra de los valores detrás de los identificadores.
    • Pero el compilador no ver macros del preprocesador. E incluso si esto no fuera así, es difícil encontrar un ejemplo en cualquier lenguaje de programación, donde el código fuente se compila hasta que uno de los cambios de algunos int constante de 1 a 2. Esto es simplemente inaceptable e inexplicable, mientras que se ven muy buenas explicaciones aquí el por qué de que el código está roto, incluso con los mismos valores.
    • Sí, la compila no ver macros. Pero, fue esto la pregunta?
    • Su respuesta es que falta el punto, lea harmínico la respuesta y el OP que lo comente.
    • «E incluso si esto no fuera así, es difícil encontrar un ejemplo en cualquier lenguaje de programación, donde el código fuente se compila hasta que uno de los cambios de algunos int constante de 1 a 2». Esta es fácil :-). Sólo trato de compilar este: CONST A = 1; CONST B = 2; CONST C = B DIV (2-A); a continuación, cambie CONST A = 1 a CONST A = 2 y a ver si el compilador compila 🙂 :-).
    • ¿En qué idioma?
    • PASCAL / Delphi / Object Pascal
    • Se quejan de que? A continuación, se rompen, en mi humilde opinión. Me gustaría aceptar, incluso de bienvenida, una advertencia de que hay un cero a la división. Pero no un rechazo a compilarlo.
    • ¿Qué valor debe C, a continuación, tienen en declaraciones de las que uso? Como pasar como parámetro a una función que acepta un entero? Cómo debe ser la del compilador compila esta declaración: SomeProcedure(a,B,C);
    • Oh – y Microsoft compilador de C# se queja con la División por la constante cero» en este código: private const int a = 2; private const int B = 2; private const int C = B/(2 – A); mientras que este código funciona como se esperaba: private const int a = 1; private const int B = 2; private const int C = B/(2 – A); Así que el único cambio para que no compile fue modificar una definición de una constante de 1 a 2 :-).
    • Se podría hacer SomeProcedure(A, B, B DIV (2-A)). De todos modos, si el lenguaje de los estados que CONST debe ser evaluada en tiempo de compilación, entonces, por supuesto, mi reclamo no es válido para este caso. Ya que de alguna manera se difumina la distinción de tiempo de compilación y tiempo de ejecución. También observe si escribimos CONST C = X(2-A); FUNCTION X:INTEGER(CONST Y:INTEGER) = B/Y; ?? O son funciones no permitidas?
    • CONST expresiones (en PASCAL y C#) debe estar completamente evaluables en tiempo de compilación, por lo que ninguna de las llamadas de función están permitidos. Y además – aunque el compilador para compilar como SomeProcedure(a, B, B DIV (2-A)) todavía no se compilará todo porque he cambiado algunos int constante de 1 a 2. Así que no es difícil encontrar ejemplos que impiden la compilación del archivo de origen cuando puedo cambiar la definición de una constante de 1 a 2 :-).
    • ser totalmente evaluables en tiempo de compilación, así que no hay función se permiten las llamadas» – esto es un non sequitur, especialmente cuando el compilador conoce la función y sólo contiene el código que se permite en expresiones constantes. – Sin embargo, debo aceptar que mi afirmación era fácil de refutar. Aún así, no creo que esto es como debe ser.
    • Touché :-). Me deja modificarlo, entonces: CONST expresiones (en PASCAL y C#) debe estar completamente evaluables por el compilador sin la ayuda de un código definido por el usuario, en tiempo de compilación, por lo que ninguna función definida por el usuario se permiten las llamadas (pero f.ex. ABS, SQRT y otros integrados en funciones intrínsecas están permitidos, porque son compilador suministrado por la «magia» de las funciones y no de código real que se llama a una subrutina – al menos en PASCAL/Delphi).
    • Puedo ver por qué ellos no lo permiten. Uno podría fácilmente escribir los programas completos en CONST FUNCIONES (tenga en cuenta que los lenguajes funcionales como Haskell proporcionar sólo tales funciones, aparte de las primitivas) y podría hacer los cálculos enteramente en el compilador. Eso sería divertido. 🙂
    • Yet, we feel that compilation should not depend on the value of a literal. Meh. Usted puede hacer un montón de cosas interesantes con literales, especialmente en la plantilla de meta-programación, pero obviamente no todos literal siempre va a tener sentido. También, creo que tu respuesta pierde el punto un poco. Pero gracias por la discusión de todos modos.
    • Fue escrito en la suposición de que ahora está claro por qué f(i = 1, i = 2) es justamente indefinido, e intenta dar una práctica (no técnicos) razón por la que no hacer una excepción de la regla en el caso especial f(i = 1, i = 1) (como alguien de por aquí se sugiere).

  4. 11

    La confusión es que el almacenamiento de un valor constante a una variable local no es uno atómica de la instrucción en cada arquitectura de la C está diseñado para ejecutarse en. El procesador ejecuta el código en el que importa más que el compilador en este caso. Por ejemplo, en el BRAZO donde cada instrucción no puede llevar un completo de 32 bits constante, el almacenamiento de int en una variable necesita más que una instrucción. Ejemplo con este pseudo-código, donde sólo puede almacenar 8 bits al mismo tiempo y deben trabajar en una versión de 32 bits de registro, que es un int32:

    reg = 0xFF; //first instruction
    reg |= 0xFF00; //second
    reg |= 0xFF0000; //third
    reg |= 0xFF000000; //fourth
    i = reg; //last

    Usted puede imaginar que si el compilador quiere optimizar puede intercalar la misma secuencia dos veces, y no sé qué valor será escrito a i; y digamos que no es muy inteligente:

    reg = 0xFF;
    reg |= 0xFF00;
    reg |= 0xFF0000;
    reg = 0xFF;
    reg |= 0xFF000000;
    i = reg; //writes 0xFF0000FF == -16776961
    reg |= 0xFF00;
    reg |= 0xFF0000;
    reg |= 0xFF000000;
    i = reg; //writes 0xFFFFFFFF == -1

    Sin embargo, en mis pruebas de gcc es lo suficientemente amable como para reconocer que se usa el mismo valor dos veces y genera una vez y no hace nada raro. Puedo obtener -1, -1
    Pero mi ejemplo es todavía válida como es importante tener en cuenta que incluso una constante no puede ser tan obvio como parece ser.

    • Supongo que en el BRAZO el compilador simplemente carga la constante de una tabla. Lo que usted describe parece más como MIPS.
    • Sí, pero en un caso cuando no simplemente -1 (que el compilador ha almacenado en algún lugar), pero es más bien 3^81 mod 2^32, pero constante, el compilador podría hacer exactamente lo que se hace aquí, y en algunos palanca de omtimization, mi interleave la llamada secuencias para evitar la espera.
    • sí, lo he comprobado ya. De hecho, el compilador es demasiado inteligente para cargar cada constante de una tabla. De todos modos, que nunca iba a utilizar el mismo registro para el cálculo de la dos constantes. Esto sería simplemente como seguramente ‘anular la definición de’ la define el comportamiento.
    • Pero usted está probablemente no «cada compilador de C++ programador en el mundo». Recuerde que hay máquinas con 3 cortos de los registros disponibles para sus cálculos sólo.
    • considere el ejemplo f(i = A, j = B) donde i y j son dos objetos separados. Este ejemplo no tiene la UB. Máquina que tiene 3 registros no es excusa para el compilador para la mezcla de los dos valores de A y B en el mismo registro (como se muestra en @davidf de la respuesta), porque se rompería el programa de la semántica.
    • Esta respuesta es una tontería. Esto es una pura cuestión acerca de la norma. Lo que cualquier plataforma, no o no es irrelevante. Incluso si no existía ninguna plataforma en la que esto podría fallar, el comportamiento sería todavía indefinido debido a que la norma dice claramente que no lo definen.

  5. 11

    Comportamiento se especifica comúnmente como indefinido si hay alguna razón concebible por qué un compilador que estaba tratando de ser «útil», podría hacer algo que haría totalmente inesperado comportamiento.

    En el caso de que una variable está escrito varias veces con nada para asegurarse de que las escrituras suceder en distintas épocas, algunos tipos de hardware que podría permitir que varios «tienda» de operaciones a ser realizadas simultáneamente a diferentes direcciones con un dual-puerto de memoria. Sin embargo, algunos de doble puerto de recuerdos prohíben expresamente el escenario donde dos almacenes éxito de la misma dirección de forma simultánea, independientemente de si o no los valores escritos partido. Si un compilador para una máquina avisos de dos unsequenced intenta escribir la misma variable, es posible que se niega a compilar o asegurarse de que los dos escribe no puede conseguir programado simultáneamente. Pero si uno o ambos de los accesos es a través de un puntero o una referencia, el compilador no siempre puede ser capaz de decirle si escribe podría golpear la misma ubicación de almacenamiento. En ese caso, se puede programar el escribe simultáneamente, causando un hardware trampa en el intento de acceso.

    Por supuesto, el hecho de que alguien pueda implementar un compilador de C en dicha plataforma, no sugieren que este tipo de comportamiento no debe ser definido en plataformas de hardware cuando el uso de tiendas de tipos lo suficientemente pequeño como para ser procesado de forma atómica. Tratando de almacenar dos valores diferentes en unsequenced de la moda podría causar extrañeza si un compilador no es consciente de ello; por ejemplo, dado que:

    uint8_t v;  //Global
    
    void hey(uint8_t *p)
    {
      moo(v=5, (*p)=6);
      zoo(v);
      zoo(v);
    }

    si el compilador en las líneas de la llamada «moo» y puede decir lo que no modifica
    «v», es posible almacenar un 5 a v, entonces la tienda de un 6 a *p, luego del paso 5 en «zoo»,
    y, a continuación, pasar el contenido de v a «zoo». Si el «zoológico» no modifica «v»,
    no debe ser de ninguna manera las dos llamadas se debe pasar diferentes valores,
    pero que fácilmente podría suceder de todos modos. Por otro lado, en los casos donde
    ambas tiendas escribir el mismo valor, rareza no podía ocurrir y
    hay en la mayoría de plataformas haber ninguna razón sensata para una implementación
    a hacer nada raro. Desafortunadamente, algunos compilador escritores no se necesita ningún
    excusa para tontos comportamientos más allá de «porque la Norma lo permite», por lo que incluso
    esos casos no son seguras.

  6. 9

    El hecho de que el resultado sería el mismo en la mayoría de las implementaciones en este caso fortuito; el orden de evaluación es aún indefinido. Considere la posibilidad de f(i = -1, i = -2): aquí, el orden importa. La única razón por la que no importa en que su ejemplo es el accidente que ambos valores son -1.

    Dado que la expresión se especifica como uno con un comportamiento indefinido, un maliciosamente compatible con el compilador podría mostrar una imagen inapropiada al evaluar f(i = -1, i = -1) y abortar la ejecución – y todavía se puede considerar totalmente correcta. Por suerte, no compiladores soy consciente de ello.

  7. 8

    A mí me parece que la única regla relativa a la secuenciación de los argumentos de la función de la expresión está aquí:

    3) Cuando se llama a una función (si o no la función es en línea, y si es o no explícita llamada a la función se utiliza la sintaxis), cada valor de la computación y los efectos secundarios asociados con cualquier argumento de la expresión, o con el postfix expresión que designa a la llamada de la función, se ordenan antes de la ejecución de cada expresión o instrucción en el cuerpo de la función llamada.

    Esto no define la secuenciación entre el argumento de expresiones, por lo que terminamos en este caso:

    1) Si un efecto secundario de un escalar objeto es unsequenced en relación a otro efecto secundario en el mismo escalar el objeto, el comportamiento es indefinido.

    En la práctica, en la mayoría de los compiladores, en el ejemplo citado se ejecuta bien (como opuesto a «borrar el disco duro» y otros teóricos comportamiento indefinido consecuencias).

    Es, sin embargo, una responsabilidad, ya que depende del compilador específico de comportamiento, incluso si los dos valores asignados son los mismos. También, obviamente, si se trató de asignar valores diferentes, el resultado sería «verdaderamente» undefined:

    void f(int l, int r) {
        return l < -1;
    }
    auto b = f(i = -1, i = -2);
    if (b) {
        formatDisk();
    }
  8. 8

    C++17 define más estrictas reglas de evaluación. En particular, las secuencias de los argumentos de la función (aunque sin especificar el fin).

    N5659 §4.6:15

    Evaluaciones Un y B son indeterminately secuenciado cuando Un es la secuencia antes de B o B es la secuencia antes de Un,
    pero no se especifica cual. [ Nota: Indeterminately secuenciado las evaluaciones no se superponen, sino que podría
    se ejecuta en primer lugar. —nota final de la ]

    N5659 § 8.2.2:5

    El
    la inicialización de un parámetro, incluyendo todos los asociados el valor de cálculo y efectos secundarios, es indeterminately
    ordenan con respecto a la de cualquier otro parámetro.

    Permite que algunos de los casos que sería UB antes:

    f(i = -1, i = -1); //value of i is -1
    f(i = -1, i = -2); //value of i is either -1 or -2, but not specified which one
    • Gracias por añadir esta actualización para c++17, así que no tienes que. 😉
    • Impresionante, muchas gracias por esta respuesta. Ligero de seguimiento: si f‘s de la firma fueron f(int a, int b), ¿ C++17 de garantizar que a == -1 y b == -2 si se llama como en el segundo caso?
    • Sí. Si tenemos parámetros a y b, luego i-entonces-a se inicializan a -1, luego i-entonces-b se inicializan a -2, o al revés. En ambos casos, nos encontramos con a == -1 y b == -2. Al menos eso es lo que he leído «La inicialización de un parámetro, incluyendo todos los asociados el valor de cálculo y efectos secundarios, es indeterminately secuenciado con respecto a la de cualquier otro parámetro de«.
    • Creo que ha sido el mismo en C desde siempre.
  9. 5

    El operador de asignación podría ser sobrecargados, en cuyo caso el pedido se pudo asunto:

    struct A {
        bool first;
        A () : first (false) {
        }
        const A & operator = (int i) {
            first = !first;
            return * this;
        }
    };
    
    void f (A a1, A a2) {
        //...
    }
    
    
    //...
    A i;
    f (i = -1, i = -1);   //the argument evaluated first has ax.first == true
    • Lo cierto, pero la pregunta era acerca de tipos escalares, que otros han señalado significa esencialmente de tipo int de la familia, de flotación de la familia, y los punteros.
    • El verdadero problema en este caso es que el operador de asignación es el estado, por lo que incluso regular la manipulación de la variable es propenso a problemas como este.
  10. 2

    Esto es sólo responder a la «no estoy seguro de lo que «escalar el objeto» podría significar, además de algo así como un int o float».

    Me gustaría interpretar el «escalar el objeto» como una abreviatura de «escalar tipo de objeto», o simplemente «escalar de variable de tipo». Entonces, pointer, enum (constante) son de tipo escalar.

    Este es un artículo de MSDN de Tipos Escalares.

    • Esto lee un poco como un «vínculo única respuesta». Puede copiar los correspondientes bits de enlace a esta respuesta (en una blockquote)?
    • Este no es un vínculo única respuesta. El enlace es sólo para una explicación más detallada. Mi respuesta es «puntero», «enum».
    • Yo no dije que tu respuesta fue un enlace única respuesta. Me dijo que «se lee como [una]». Le sugiero que lea por qué no queremos que el enlace sólo respuestas en la sección de ayuda. La razón de ser, si las actualizaciones de Microsoft de su Url en su sitio, que el vínculo se rompe.
  11. 1

    De hecho, hay una razón para que no dependen del hecho de que el compilador comprueba que i se le asigna el mismo valor dos veces, por lo que es posible reemplazar con una sola asignación. Lo que si tenemos algunas expresiones?

    void g(int a, int b, int c, int n) {
        int i;
        //hey, compiler has to prove Fermat's theorem now!
        f(i = 1, i = (ipow(a, n) + ipow(b, n) == ipow(c, n)));
    }
    • No es necesario demostrar el teorema de Fermat: simplemente asignar 1 a i. Ambos argumentos asignar 1 y esto hace que el «derecho» de la cosa, o los argumentos asignar valores diferentes y es un comportamiento indefinido así que nuestra elección es todavía permitido.

Dejar respuesta

Please enter your comment!
Please enter your name here