aquí hay un ejemplo
#include <iostream>
using namespace std;
int main()
{
int x = 0;
cout << (x == 0 ? x++ : x) << endl; //operator in branch
cout << "x=" << x << endl;
cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
cout << "x=" << x << endl;
return 0;
}
producción:
0
x=1
1
x=1
Entiendo la salida, pero ¿Es este comportamiento indefinido o no? ¿Está garantizado el orden de evaluación en cualquiera de los dos casos?
Incluso si está garantizado, soy muy consciente de que el uso de incrementos/decrementos puede convertirse rápidamente en un problema de legibilidad. Solo pregunto porque vi un código similar y no estaba seguro de inmediato, dado que hay muchos ejemplos de uso ambiguo/indefinido de operadores de incremento/decremento, como…
-
C++ no define el orden en que se evalúan los parámetros de la función. ↪
int nValue = Add(x, ++x);
-
El lenguaje C++ dice que no puede modificar una variable más de una vez entre puntos de secuencia. ↪
x = ++y + y++
-
Dado que los operadores de incremento y decremento tienen efectos secundarios, el uso de expresiones con operadores de incremento o decremento en una macro de preprocesador puede tener resultados no deseados. ↪
#define max(a,b) ((a)<(b))?(b):(a) k = max( ++i, j );
CT
Para el operador condicional (§5.16 [expr.cond]/p1):
Cada cálculo de valor y efecto secundario asociado con la primera expresión se secuencia antes de cada cálculo de valor y efecto secundario asociado con la segunda o tercera expresión.
Para el operador lógico OR (§5.15 [expr.log.or]/p1-2):
el segundo operando no se evalúa si el primer operando se evalúa como
true
.
[…] Si se evalúa la segunda expresión, cada cálculo de valor y efecto secundario asociado con la primera expresión se secuencia antes de cada cálculo de valor y efecto secundario asociado con la segunda expresión.
El comportamiento de su código está bien definido.
Vaca Efectiva
Existe un orden de ejecución garantizado en operadores ternarios y booleanos &&
y ||
operaciones, por lo que no hay conflicto en los puntos de secuencia de evaluación.
Uno a la vez
cout << (x == 0 ? x++ : x) << endl; //operator in branch
Siempre saldrá x
pero lo incrementará solo si fuera 0.
cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
Esto también está bien definido, si x
fuera 1 no evaluará el RHS, si no lo fuera lo decrementará pero --x
nunca será 0, por lo que será verdadero iff x==1, en cuyo caso x
ahora también será 0.
En el último caso si x es INT_MIN
no es un comportamiento bien definido para disminuirlo (y se ejecutaría).
Eso no puede suceder en el primer caso donde x no será 0 si es INT_MAX
para que estés a salvo.
-
Edge case, pero por supuesto el segundo fragmento de código tendría UB si
x
esINT_MIN
cuando se ejecuta. El problema equivalente conINT_MAX
no puede afectar el primer fragmento.–Steve Jessop
8 sep 2014 a las 14:06
trucos
Entiendo el resultado, pero ¿es este comportamiento indefinido o no?
El código está perfectamente definido. El estándar C11 dice:
6.5.15 Operador condicional
Se evalúa el primer operando; hay un punto de secuencia entre su evaluación y la evaluación del segundo o tercer operando (lo que se evalúe). El segundo operando se evalúa solo si el primero se compara diferente a
0
; el tercer operando se evalúa solo si el primero se compara igual a0
; el resultado es el valor del segundo o tercer operando (el que se evalúe), convertido al tipo descrito a continuación.110)
6.5.14 Operador lógico OR
A diferencia de bit a bit
|
operador, la||
el operador garantiza la evaluación de izquierda a derecha; si se evalúa el segundo operando, hay un punto de secuencia entre las evaluaciones del primer y segundo operandos. Si el primer operando se compara desigual a0
el segundo operando no se evalúa.
Más lejos wiki lo explica con un ejemplo:
Entre la evaluación de los operandos izquierdo y derecho del
&&
(Y lógico),||
(OR lógico) (como parte de la evaluación de cortocircuito), ycomma operators
. Por ejemplo, en la expresión*p++ != 0 && *q++ != 0
todos los efectos secundarios de la subexpresión*p++ != 0
se completan antes de cualquier intento de accederq
.Entre la evaluación del primer operando del operador ternario “signo de interrogación” y el segundo o tercer operando. Por ejemplo, en la expresión
a = (*p++) ? (*p++) : 0
hay un punto de secuencia después del primero*p++
lo que significa que ya se ha incrementado en el momento en que se ejecuta la segunda instancia.
La regla para ||
y ?:
es lo mismo para C++ (sección 5.15 y 5.16) que en C.
¿Está garantizado el orden de evaluación en cualquiera de los dos casos?
Sí. El orden de evaluación de los operandos de los operadores. ||
, &&
, ,
y ?:
se garantiza que es de izquierda a derecha.
-
@Slava; ¡Vaya! ¿Cómo es engañoso? Podrías explicar ?
– trucos
08/09/2014 a las 14:00
-
Hay 2 ejemplos en el código, el segundo tiene UB imo. Podría estar equivocado, pero la respuesta no responde de todos modos, ya que se basa en si el operador booleano tiene un punto de secuencia.
– Eslava
08/09/2014 a las 14:00
-
@Slava; ¿Cuál tiene UB?
– trucos
8 de septiembre de 2014 a las 14:04
-
@haccks no hay UB Me equivoqué. Pero su respuesta no menciona por qué el segundo ejemplo no tiene UB, es decir, hay un punto de secuencia en el operador booleano ||
– Eslava
08/09/2014 a las 14:09
ani627
En C, el valor almacenado de un objeto se puede modificar solo una vez entre dos puntos de secuencia.
Se produce un punto de secuencia:
- Al final de la expresión completa.
- En el
&&
,||
y?:
operadores - En una llamada de función.
Entonces, por ejemplo, esta expresión x = i++ * i++
es indefinidomientras x = i++ && i++
es perfectamente legal.
Tu código muestra comportamiento definido.
intx=0;
cout
En la expresión anterior x
es 0
asi que x++
se ejecutará, aquí x ++ es un incremento posterior, por lo que generará 0
.
cout
En la expresión anterior como x
ahora tiene valor 1
por lo que la salida será 1
.
cout
Aquí x
es 1
por lo que la siguiente condición no se evalúa (--x == 0
) y la salida será 1
.
cout
como la expresión --x == 0
no se evalúa, la salida volverá a ser 1
.
Nicolás Miller
Sí, es seguro usar los operadores de incremento/decremento como los tiene. Esto es lo que está sucediendo en su código:
Fragmento #1
cout << (x == 0 ? x++ : x) << endl; //operator in branch
En este fragmento, está probando si x == 0
cual es true
. Ya que es true
su expresión ternaria evalúa el x++
. Dado que está utilizando un incremento posterior aquí, el valor original para x
se imprime en el flujo de salida estándar, y entonces x
se incrementa.
Fragmento #2
cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
Este fragmento es un poco más confuso, pero aun así arroja un resultado predecible. En este punto, x = 1
del primer fragmento. En la expresión ternaria, la parte condicional se evalúa primero; sin embargo, debido a Cortocircuito, la segunda condición, --x == 0
nunca se evalúa.
Para C++ los operadores ||
y &&
son los operadores booleanos de cortocircuito para O lógico y Y lógico respectivamente. Cuando utiliza estos operadores, sus condiciones se verifican (de izquierda a derecha) hasta que se pueda determinar el resultado final. Una vez que se determina el resultado, no se verifican más condiciones.
Mirando el fragmento #2, su primera condición verifica si x == 1
. Dado que su primera condición se evalúa como true
y está usando el OR lógico, no hay necesidad de seguir evaluando otras condiciones. Eso significa que --x == 0
es nunca ejecutado.
Una nota al margen rápida sobre el cortocircuito:
El cortocircuito es útil para aumentar el rendimiento de su programa. Suponga que tiene una condición como esta que llama a varias funciones que consumen mucho tiempo:
if (task1() && task2())
{
//...Do something...
}
En este ejemplo, task2
Nunca debe ser llamado a menos que task1
se completa con éxito (task2
depende de algunos datos que son alterados por task1
).
porque estamos usando un Operador AND en cortocircuitosi task1
falla al regresar false
, entonces la declaración if tiene la información suficiente para salir antes y dejar de verificar otras condiciones. Esto significa que task2
nunca se llama.
Relacionado (no duplicado): stackoverflow.com/questions/10995445/… – Describe un caso especial de asignación de nuevo en la variable incrementada.
– Suma
08/09/2014 a las 13:59
Nota: Bien definida es solo una pregunta. Mantenible es otro. Si tiene que preguntarnos, ¿cómo se asegurará la próxima persona que lea ese código de que es seguro? “Los verdaderos escritores reescriben para evitar el problema”.
– keshlam
9 de septiembre de 2014 a las 3:12
El operador de decremento en la línea 4 de
main()
es irrelevante en este ejemplo porque el comportamiento de cortocircuito de||
hará que el--x
para ser salteado por completo.– JLRishe
9 de septiembre de 2014 a las 7:43
@JLRishe, en realidad, la pregunta gira en torno a eso: ¿el cortocircuito está garantizado de tal manera que
--x
nunca se evalúa? (Respondido abajo)– jozxyqk
9 de septiembre de 2014 a las 7:53
@jozxyqk Ok, pero en ese caso, no tiene nada que ver con operadores ternarios/condicionales y todo que ver con el
||
operador.x == 1 || --x == 0
se evalúa completamente antes de que el operador condicional tenga alguna participación y en ese momento, el--x
ya se habrá saltado. En otras palabras, la línea 4 no nos dice nada que no sea trivial acerca de los operadores condicionales.– JLRishe
9 de septiembre de 2014 a las 7:55