Recientemente en una entrevista hubo una siguiente pregunta de tipo objetivo.
int a = 0;
cout << a++ << a;
Respuestas:
a. 10
b. 01
c. comportamiento indefinido
Respondí la opción b, es decir, la salida sería "01".
Pero para mi sorpresa más tarde, un entrevistador me dijo que la respuesta correcta es la opción c: indefinida.
Ahora, conozco el concepto de puntos de secuencia en C ++. El comportamiento no está definido para la siguiente declaración:
int i = 0;
i += i++ + i++;
pero según mi entendimiento de la declaración cout << a++ << a
, ostream.operator<<()
se llamaría dos veces, primero con ostream.operator<<(a++)
y después ostream.operator<<(a)
.
También verifiqué el resultado en el compilador VS2010 y su salida también es '01'.
10
, sería una01
o la otra00
. (c++
siempre evaluará el valor quec
tenía antes de ser incrementado). E incluso si no estuviera indefinido, sería terriblemente confuso.Respuestas:
Tu puedes pensar en:
Como:
C ++ garantiza que todos los efectos secundarios de evaluaciones anteriores se habrán realizado en puntos de secuencia . No hay puntos de secuencia entre la evaluación de argumentos de función, lo que significa que el argumento
a
se puede evaluar antesstd::operator<<(std::cout, a++)
o después del argumento . Entonces, el resultado de lo anterior no está definido.Actualización de C ++ 17
En C ++ 17 se han actualizado las reglas. En particular:
Lo que significa que requiere que el código produzca un resultado
b
, que genera01
.Consulte P0145R3 Refinando el orden de evaluación de expresiones para Idiomatic C ++ para obtener más detalles.
fuente
c
tiene tipoint
,operator<<
aquí están las funciones miembro.operator<<
es una función miembro o una función independiente, no afecta a los puntos de secuencia.So the result of the above is undefined.
Su explicación solo es buena para lo no especificado , no para lo indefinido . Sin embargo, James Kanze explicó cómo es más indefinido en su respuesta .Técnicamente, en general, esto es un comportamiento indefinido .
Pero hay dos aspectos importantes de la respuesta.
La declaración de código:
se evalúa como:
El estándar no define el orden de evaluación de los argumentos de una función.
Entonces O bien:
std::operator<<(std::cout, a++)
se evalúa primero oa
se evalúa primero oEste pedido no está especificado [Ref 1] según el estándar.
[Ref 1] C ++ 03 5.2.2 Llamada de función,
párrafo 8
Además, no hay un punto de secuencia entre la evaluación de argumentos a una función, pero existe un punto de secuencia sólo después de la evaluación de todos los argumentos [Ref 2] .
[Ref 2] C ++ 03 1.9 Ejecución del programa [intro.execution]:
Para 17:
Tenga en cuenta que, aquí
c
se accede al valor de más de una vez sin un punto de secuencia intermedio, con respecto a esto, el estándar dice:[Ref 3] C ++ 03 5 Expresiones [expr]:
Para 4:
El código se modifica
c
más de una vez sin intervenir un punto de secuencia y no se accede a él para determinar el valor del objeto almacenado. Esta es una clara violación de la cláusula anterior y, por lo tanto, el resultado según lo ordena el estándar es un comportamiento indefinido [Ref 3] .fuente
Los puntos de secuencia solo definen un orden parcial . En su caso, tiene (una vez que se realiza la resolución de sobrecarga):
Hay un punto de secuencia entre
a++
y la primera llamada astd::ostream::operator<<
, y hay un punto de secuencia entre la segundaa
y la segunda llamada astd::ostream::operator<<
, pero no hay un punto de secuencia entrea++
ya
; las únicas restricciones de orden son quea++
se evalúen por completo (incluidos los efectos secundarios) antes de la primera llamada aoperator<<
, y que la segundaa
se evalúe completamente antes de la segunda llamada aoperator<<
. (También existen restricciones de ordenoperator<<
causales : la segunda llamada a no puede preceder a la primera, ya que requiere los resultados de la primera como argumento). §5 / 4 (C ++ 03) establece:Uno de los ordenamientos permisibles de su expresión es
a++
,a
la primera llamada aoperator<<
la segunda llamada aoperator<<
; esto modifica el valor almacenado dea
(a++
) y accede a él de otra manera que para determinar el nuevo valor (el segundoa
), el comportamiento no está definido.fuente
c
fuera un tipo de usuario con un usuario definido++
, en lugar deint
, los resultados no serían especificados, pero no habría un comportamiento indefinido.c
enfoo(foo(bar(c)), c)
? Hay un punto de secuencia cuando se llama a las funciones y cuando regresan, pero no se requiere una llamada de función entre las evaluaciones de los dosc
.c
fuera un UDT, los operadores sobrecargados serían llamadas a funciones e introducirían un punto de secuencia, por lo que el comportamiento no sería indefinido. Pero aún estaría sin especificar si la subexpresiónc
se evaluó antes o despuésc++
, por lo que no se especificaría si obtuvo la versión incrementada o no (y en teoría, no tendría que ser el mismo cada vez).c
yc++
, por lo tanto, las dos pueden aparecer en cualquier orden. En cuanto al punto y coma ... Sólo provocan un punto de secuencia en la medida en que sean expresiones completas. Otros puntos de secuencia importantes son la llamada a la función:f(c++)
verá el incrementoc
enf
, y el operador de coma&&
,||
y?:
también provocará puntos de secuencia.La respuesta correcta es cuestionar la pregunta. La declaración es inaceptable porque el lector no puede ver una respuesta clara. Otra forma de verlo es que hemos introducido efectos secundarios (c ++) que hacen que la declaración sea mucho más difícil de interpretar. El código conciso es genial, siempre que su significado sea claro.
fuente