tutorial studio programacion móviles español desarrollo curso aprender aplicaciones c++ order-of-evaluation sequence-points order-of-operations

c++ - studio - ¿Está este código bien definido?



solidity español (6)

Como una pequeña prueba, considere:

#include <iostream> struct X { const X& f(int n) const { std::cout << n << ''/n''; return *this; } }; int main() { int n = 1; X x; x.f(++n).f(++n).f(++n).f(++n); }

Ejecuto esto con gcc 3.4.6 y sin optimización y obtengo:

5 4 3 2

... con -O3 ...

2 3 4 5

Entonces, ya sea que la versión 3.4.6 tuvo un error mayor (que es un poco difícil de creer), o la secuencia no está definida como sugirió Philip Potter. (GCC 4.1.1 con / sin -O3 producido 5, 5, 5, 5).

EDITAR - mi resumen de la discusión en los comentarios a continuación:

  • 3.4.6 realmente podría haber tenido un error (bueno, sí)
  • muchos compiladores más nuevos producen 5/5/5/5 ... ¿es eso un comportamiento definido?
    • probablemente no, ya que corresponde a que todos los efectos secundarios de incremento se "accionen" antes de que se realice cualquiera de las llamadas de función, lo cual no es un comportamiento que nadie haya sugerido aquí pueda estar garantizado por el Estándar
  • este no es un muy buen enfoque para investigar los requisitos de la Norma (particularmente con un compilador más antiguo como 3.4.6): acordado, pero es una prueba de cordura útil

Este código está tomado de una discusión que está ocurriendo here .

someInstance.Fun(++k).Gun(10).Sun(k).Tun();

¿Está este código bien definido? ¿Está ++k en Fun () evaluado antes de k en Sun ()?

¿Qué pasa si k es un tipo definido por el usuario, no un tipo incorporado? Y de qué manera la función anterior llama al orden es diferente de esto:

eat(++k);drink(10);sleep(k);

Por lo que sé, en ambas situaciones, existe un punto de secuencia después de cada llamada de función . Si es así, ¿por qué no puede el primer caso también estar bien definido como el segundo?

La sección 1.9.17 de la norma C ++ ISO dice esto sobre la secuencia de puntos y la evaluación de la función:

Al llamar a una función (ya sea que la función esté o no en línea), hay un punto de secuencia después de la evaluación de todos los argumentos de la función (si corresponde) que tiene lugar antes de la ejecución de cualquier expresión o declaración en el cuerpo de la función . También hay un punto de secuencia después de la copia de un valor devuelto y antes de la ejecución de cualquier expresión fuera de la función .


Creo que si lees exactamente lo que dice la cita estándar, el primer caso no estará bien definido:

Al llamar a una función (ya sea que la función esté o no en línea), hay un punto de secuencia después de la evaluación de todos los argumentos de la función (si corresponde) que tiene lugar antes de la ejecución de cualquier expresión o declaración en el cuerpo de la función.

Lo que esto nos dice no es que "lo único que puede suceder después de que se hayan evaluado los argumentos para una función es la llamada a la función real", sino simplemente que hay un punto de secuencia en algún momento después de que finalice la evaluación de los argumentos, y antes de la llamada de la función.

Pero si te imaginas un caso como este:

foo(X).bar(Y)

La única garantía que esto nos da es que:

  • X se evalúa antes de la llamada a foo , y
  • Y se evalúa antes de la llamada a la bar .

Pero una orden como esta todavía sería posible:

  1. evaluar X
  2. Evaluar Y
  3. (punto de secuencia que separa a X de la llamada foo )
  4. llama foo
  5. .
  6. bar llamadas

y, por supuesto, también podríamos intercambiar los dos primeros elementos, evaluando Y antes de X Por qué no? El estándar solo requiere que los argumentos para una función se evalúen completamente antes de la primera declaración del cuerpo de la función, y las secuencias anteriores satisfacen ese requisito.

Esa es mi interpretación, al menos. No parece decir que no puede ocurrir nada más entre la evaluación del argumento y el cuerpo de la función, solo que esos dos están separados por un punto de secuencia.


El segundo caso es ciertamente bien definido. Una cadena de tokens que termina con un punto y coma es una declaración atómica en C ++. Cada declaración se analiza, procesa y completa antes de que comience la siguiente declaración.


Este es un comportamiento indefinido, porque el valor de k se modifica y se lee en la misma expresión, sin un punto de secuencia intermedio. Vea la excelente respuesta larga a esta pregunta .

La cita de 1.9.17 le dice que todos los argumentos de la función se evalúan antes de llamar al cuerpo de la función, pero no dice nada sobre el orden relativo de evaluación de los argumentos a diferentes llamadas de función dentro de la misma expresión, sin garantía de que "++ k Fun () se evalúa antes de k en Sun ()".

eat(++k);drink(10);sleep(k);

es diferente porque el ; Es un punto de secuencia, por lo que el orden de evaluación está bien definido.


Esto depende de cómo se define el Sun . Lo siguiente está bien definido.

struct A { A &Fun(int); A &Gun(int); A &Sun(int&); A &Tun(); }; void g() { A someInstance; int k = 0; someInstance.Fun(++k).Gun(10).Sun(k).Tun(); }

Si cambia el tipo de parámetro de Sun a int , se vuelve indefinido. Dibujemos un árbol de la versión tomando un int .

<eval body of Fun> | % // pre-call sequence point | { S(increment, k) } <- E(++x) | E(Fun(++k).Gun(10)) | .------+-----. .-- V(k)--%--<eval body of Sun> / / / E(Fun(++k).Gun(10).Sun(k)) | .---------+---------. / / E(Fun(++k).Gun(10).Sun(k).Tun()) | % // full-expression sequence point

Como se puede ver, tenemos una lectura de k (designada por V(k) ) y un efecto secundario sobre k (en la parte superior) que no están separados por un punto de secuencia: en esta expresión, relativa a cada otra sub -expresión, no hay ningún punto de secuencia en absoluto. El muy bajo % significa el punto de secuencia de expresión completa.


Sé que el comportamiento de los compiladores realmente no puede probar nada, pero pensé que sería interesante ver qué daría la representación interna de un compilador (aún un nivel un poco más alto que la inspección de ensamblaje).

He usado la demostración en línea de Clang / LLVM con este código:

#include <stdio.h> #include <stdlib.h> struct X { X const& f(int i) const { printf("%d/n", i); return *this; } }; int main(int argc, char **argv) { int i = 0; X x; x.f(++i).f(++i).f(++i); // line 16 }

Y compilado con las optimizaciones estándar (en modo C ++), dio:

/tmp/webcompile/_13371_0.cc: En la función ''int main (int, char **)'':
/tmp/webcompile/_13371_0.cc:16: advertencia: la operación en ''i'' puede no estar definida

lo que encontré interesante (¿algún otro compilador advirtió sobre esto? Comeau en línea no lo hizo)

Como parte aparte, también produjo la siguiente Representación intermedia (desplácese hacia la derecha):

@.str = private constant [4 x i8] c"%d/0A/00", align 1 ; <[4 x i8]*> [#uses=1] define i32 @main(i32 %argc, i8** nocapture %argv) nounwind { entry: %0 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0] ^^^^^ %1 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0] ^^^^^ %2 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0] ^^^^^ ret i32 0 }

Aparentemente, Clang se comporta como lo hace gcc 4.xx y primero evalúa todos los argumentos antes de realizar cualquier llamada de función.