¿Por qué es f(i = -1, i = -1) comportamiento indefinido?


Estaba leyendo sobre el orden de las violaciones de evaluación, y dan un ejemplo que me desconcierta.

1) Si un efecto secundario en un objeto escalar no está secuenciado en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento es indefinido.

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

En este contexto, ies un objeto escalar , que aparentemente significa

Tipos aritméticos (3.9.1), tipos de enumeración, tipos de puntero, tipos de puntero a miembro (3.9.2), std::nullptr_t y las versiones calificadas para cv de estos tipos (3.9.3) se denominan colectivamente tipos escalares.

No veo cómo la declaración es ambigua en ese caso. Me parece que, independientemente de si el primer o segundo argumento se evalúa primero, i termina como -1, y ambos argumentos también son -1.

¿Puede alguien por favor aclarar?


ACTUALIZACIÓN

Realmente aprecio toda la discusión. Hasta ahora, me gusta @harmic's answer mucho ya que expone las trampas y complejidades de definir esta declaración a pesar de lo sencillo que parece a primera vista. @acheong87 señala algunos problemas que surgen al usar referencias, pero creo que es ortogonal al aspecto de efectos secundarios no secuenciados de esta pregunta.


RESUMEN

Dado que esta pregunta recibió mucha atención, resumiré los principales puntos/respuestas. En primer lugar, permítanme una pequeña digresión para señalar que" por qué " puede tienen significados estrechamente relacionados pero sutilmente diferentes, a saber, " para qué causa", "para qué razón", y "para qué propósito". Agruparé las respuestas por cuál de esos significados de" por qué " abordaron.

Por qué causa

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

Es un comportamiento indefinido porque no se define cuál es el comportamiento.

La respuesta es en general muy buena en términos de explicar lo que dice el estándar C++. También aborda algunos casos relacionados de UB como f(++i, ++i); y f(i=1, i=-1);. En el primero de los casos relacionados, no está claro si el primer argumento debe ser i+1 y el segundo 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 casos son UB porque caen bajo los siguientes regla:

Si un efecto secundario en un objeto escalar no está secuenciado en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento es indefinido.

Por lo tanto, f(i=-1, i=-1) también es UB ya que cae bajo la misma regla, a pesar de que la intención del programador es (IMHO) obvia e inequívoca.

Paul Draper también lo hace explícito en su conclusión que

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

Lo que nos lleva a la pregunta de "¿por qué razón/propósito se dejó f(i=-1, i=-1) como comportamiento indefinido?"

Por qué razón / propósito

Aunque hay algunos descuidos (quizás descuidados) en el estándar de C++, muchas omisiones están bien razonadas y sirven para un propósito específico. Aunque soy consciente de que el propósito es a menudo "hacer el trabajo del compilador-escritor más fácil", o" código más rápido", Estaba principalmente interesado en saber si hay una buena razón dejar f(i=-1, i=-1) como UB.

Harmic y supercat proporcionan las principales respuestas que proporcionan una razón para la UB. Harmic señala que un compilador de optimización que podría dividir las operaciones de asignación aparentemente atómicas en múltiples instrucciones de la máquina, y que podría intercalar aún más esas instrucciones para una velocidad óptima. Esto podría llevar a algunos resultados muy sorprendentes: i termina como -2 en su escenario! Por lo tanto, harmic demuestra cómo asignar el mismo valor a una variable más de una vez puede tener efectos negativos si las operaciones no están secuenciadas.

Supercat proporciona una exposición relacionada de las trampas de tratar de conseguir f(i=-1, i=-1) para hacer lo que parece que debe hacer. Señala que en algunas arquitecturas, hay restricciones duras contra múltiples escrituras simultáneas en la misma dirección de memoria. Un compilador podría tener dificultades para captar esto si estuviéramos tratando con algo menos trivial que f(i=-1, i=-1).

Davidf también proporciona un ejemplo de instrucciones de intercalado muy similar a harmic.

Aunque cada uno de los ejemplos de harmic, supercat y davidf son algo inventados, tomados en conjunto, todavía sirven para proporcionar una razón tangible por la que f(i=-1, i=-1) debería ser un comportamiento indefinido.

Acepté la respuesta de Harmic porque hizo el mejor trabajo de abordar todos los significados de por qué, a pesar de que la respuesta de Paul Draper abordó la parte "por qué causa" mejor.

Otras respuestas

JohnB señala que si consideramos operadores de asignación sobrecargados (en lugar de solo escalares simples), entonces también podemos tener problemas.


Warning: Undefined property: agent_blog_content::$date_asked in /var/www/agent_etc/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 32

Warning: Undefined property: agent_blog_content::$count_answers in /var/www/agent_etc/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 52