Estaba leyendo sobre las infracciones del orden 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, i
es un objeto escalar , lo que aparentemente significa
Los tipos aritméticos (3.9.1), los tipos de enumeración, los tipos de puntero, los tipos de puntero a miembro (3.9.2), std :: nullptr_t y las versiones calificadas por 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 lo son -1
.
¿Alguien puede aclarar?
ACTUALIZAR
Realmente aprecio toda la discusión. Hasta ahora, me gusta mucho la respuesta de @ harmic, ya que expone las trampas y las complejidades de definir esta declaración a pesar de lo directa que parece a primera vista. @ acheong87 señala algunos problemas que surgen al usar referencias, pero creo que eso es ortogonal al aspecto de efectos secundarios no secuenciados de esta pregunta.
RESUMEN
Como esta pregunta recibió mucha atención, resumiré los puntos / respuestas principales. Primero, permítanme una pequeña digresión para señalar que "por qué" puede tener significados estrechamente relacionados pero sutilmente diferentes, a saber, "por qué causa ", "por qué razón " y "con qué propósito ". Agruparé las respuestas según cuál de esos significados de "por qué" abordaron.
por que causa
La respuesta principal aquí proviene 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 está definido 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 debería ser i+1
y el segundo i+2
o viceversa; en el segundo, no está claro si i
debería ser 1 o -1 después de la llamada a la función. Ambos casos son UB porque caen bajo la siguiente 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 (en mi humilde opinión) obvia e inequívoca.
Paul Draper también lo hace explícito en su conclusión de que
¿Podría haber sido un comportamiento definido? Si. ¿Fue definido? No.
lo que nos lleva a la pregunta de "¿por qué razón / propósito se f(i=-1, i=-1)
dejó como comportamiento indefinido?"
por qué razón / propósito
Aunque hay algunos descuidos (quizás descuidados) en el estándar C ++, muchas omisiones están bien razonadas y tienen un propósito específico. Aunque soy consciente de que el propósito es a menudo "facilitar el trabajo del compilador-escritor" o "código más rápido", estaba interesado principalmente en saber si hay una buena razón para dejar f(i=-1, i=-1)
UB.
harmic y supercat proporcionan las principales respuestas que proporcionan una razón para la UB. Harmic señala que un compilador optimizador que podría dividir las operaciones de asignación aparentemente atómica en múltiples instrucciones de máquina, y que podría entrelazar aún más esas instrucciones para una velocidad óptima. Esto podría conducir a algunos resultados muy sorprendentes: ¡ i
termina en -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 f(i=-1, i=-1)
de hacer lo que parece que debería hacer. Señala que en algunas arquitecturas, existen restricciones estrictas contra múltiples escrituras simultáneas en la misma dirección de memoria. Un compilador podría tener dificultades para detectar esto si estuviéramos tratando con algo menos trivial que f(i=-1, i=-1)
.
davidf también proporciona un ejemplo de instrucciones de entrelazado muy similares a las de harmic.
Aunque cada uno de los ejemplos de harmic's, supercat's y davidf 'son algo inventados, en conjunto todavía sirven para proporcionar una razón tangible de por qué f(i=-1, i=-1)
debería ser un comportamiento indefinido.
Acepté la respuesta de Harmic porque hizo el mejor trabajo al abordar todos los significados de por qué, a pesar de que la respuesta de Paul Draper abordó mejor la parte "por qué causa".
Otras respuestas
JohnB señala que si consideramos operadores de asignación sobrecargados (en lugar de simples escalares), entonces también podemos tener problemas.
f(i-1, i = -1)
o algo similar.
std::nullptr_t
y las versiones calificadas por cv de estos tipos (3.9.3) se denominan colectivamente tipos escalares . "