precedencia operadores logicos c++ c language-lawyer

c++ - logicos - precedencia de operadores en c



¿Cuál es la diferencia entre un punto de secuencia y la precedencia del operador? (3)

La precedencia del operador (y la asociatividad) establecen el orden en que se analiza y ejecuta una expresión. Sin embargo, esto no dice nada sobre el orden de evaluación de los operandos, que es un término diferente. Ejemplo:

a() + b() * c()

La precedencia del operador dicta que el resultado de b() y el resultado de c() deben multiplicarse antes de sumarse junto con el resultado de a() .

Sin embargo, no dice nada sobre el orden en el que deben ejecutarse estas funciones. El orden de evaluación de cada operador lo especifica. La mayoría de las veces, el orden de evaluación no se especifica (comportamiento no especificado), lo que significa que el estándar permite que el compilador lo haga en el orden que desee. El compilador no necesita documentar este orden ni debe comportarse de manera consistente. La razón de esto es dar a los compiladores más libertad en el análisis de expresiones, lo que significa una compilación más rápida y posiblemente también un código más rápido.

En el ejemplo anterior, escribí un programa de prueba simple y mi compilador ejecutó las funciones anteriores en el orden a() , b() , c() . El hecho de que el programa necesite ejecutar tanto b() como c() antes de poder multiplicar los resultados, no significa que deba evaluar esos operandos en cualquier orden dado.

Aquí es donde entran los puntos de secuencia. Es un punto dado en el programa donde se deben realizar todas las evaluaciones (y operaciones) anteriores. Por lo tanto, los puntos de secuencia se relacionan principalmente con el orden de evaluación y no con la precedencia del operador.

En el ejemplo anterior, los tres operandos no tienen secuencia entre sí, lo que significa que ningún punto de secuencia dicta el orden de evaluación.

Por lo tanto, se vuelve problemático cuando se introducen efectos secundarios en tales expresiones sin secuencia. Si escribimos i++ + i++ * i++ , todavía no sabemos el orden en que se evalúan estos operandos, por lo que no podemos determinar cuál será el resultado. Esto se debe a que tanto + como * tienen un orden de evaluación no especificado / sin secuencia.

Si hubiéramos escrito i++ || i++ && i++ i++ || i++ && i++ , entonces el comportamiento estaría bien definido, porque && y || especifica el orden de evaluación de izquierda a derecha y hay un punto de secuencia entre la evaluación del operando izquierdo y derecho. Por if(i++ || i++ && i++) tanto, if(i++ || i++ && i++) es un código perfectamente portátil y seguro (aunque ilegible).

En cuanto a la expresión i = i++; , el problema aquí es que el = se define como (6.5.16):

El efecto secundario de actualizar el valor almacenado del operando izquierdo se secuencia después de los cálculos de valor de los operandos izquierdo y derecho. Las evaluaciones de los operandos no son posteriores.

Esta expresión en realidad está cerca de estar bien definida, porque el texto realmente dice que el operando de la izquierda no debe actualizarse antes de calcular el operando de la derecha. El problema es la última frase: el orden de evaluación de los operandos no está especificado / no ha sido secuenciado.

Y como la expresión contiene el efecto secundario de i++ , invoca un comportamiento indefinido, ya que no podemos saber si el operando i o el operando i++ se evalúa primero.

(Hay más en eso, ya que el estándar también dice que un operando no debe usarse dos veces en una expresión para propósitos no relacionados, pero esa es otra historia).

Considere el ejemplo clásico de punto de secuencia:

i = i++;

Los estándares C y C ++ establecen que el comportamiento de la expresión anterior no está definido porque el operador = no está asociado con un punto de secuencia.

Lo que me confunde es que ++ tiene una precedencia más alta que = y, por lo tanto, la expresión anterior, basada en la precedencia, debe evaluar i++ primero y luego hacer la asignación. Por lo tanto, si comenzamos con i = 0 , siempre deberíamos terminar con i = 0 (o i = 1 , si la expresión fue i = ++i ) y no un comportamiento indefinido. ¿Qué me estoy perdiendo?


La precedencia del operador y el orden de evaluación son dos cosas diferentes. Echemos un vistazo a ellos uno por uno:

Regla de precedencia del operador: en una expresión, los operandos están más unidos a los operadores que tienen mayor prioridad.

Por ejemplo

int a = 5; int b = 10; int c = 2; int d; d = a + b * c;

En la expresión a + b * c , la prioridad de * es mayor que la de + y, por lo tanto, c se unirán a * y la expresión se analizará como a + (b * c) .

Orden de regla de evaluación: describe cómo se evaluarán los operandos en una expresión. En la declaración

d = a>5 ? a : ++a;

Se garantiza que a será evaluado antes de la evaluación de ++b o c .
Pero para la expresión a + (b * c) , aunque * tiene una precedencia más alta que la de + , no se garantiza que a se evaluará antes o después de b o c y ni siquiera c ordenados para su evaluación. Incluso a , c pueden evaluarse en cualquier orden.

La regla simple es que: la precedencia del operador es independiente del orden de evaluación y viceversa.

En la expresión i = i++ , la mayor prioridad de ++ simplemente le dice al compilador que vincule i con el operador ++ y eso es todo. No dice nada sobre el orden de evaluación de los operandos o qué efecto secundario (el uno por = operador o uno por ++ ) debe ocurrir primero. El compilador es libre de hacer cualquier cosa.

Cambiemos el nombre a la izquierda de la asignación be il y a la derecha de la asignación (en la expresión i++ ) be ir , entonces la expresión será como

il = ir++ // Note that suffix l and r are used for the sake of clarity. // Both il and ir represents the same object.

Ahora el compilador es libre de evaluar la expresión il = ir++ como

temp = ir; // i = 0 ir = ir + 1; // i = 1 side effect by ++ before assignment il = temp; // i = 0 result is 0

o

temp = ir; // i = 0 il = temp; // i = 0 side effect by assignment before ++ ir = ir + 1; // i = 1 result is 1

dando como resultado dos resultados diferentes, 0 y 1 que dependen de la secuencia de efectos secundarios por asignación y ++ y, por lo tanto, invocan UB.


Todos los operadores producen un resultado . Además, algunos operadores, como el operador de asignación = y los operadores de asignación compuesta ( += , ++ , >>= , etc.) producen efectos secundarios . La distinción entre resultados y efectos secundarios está en el centro de esta pregunta.

La precedencia del operador rige el orden en que se aplican los operadores para producir sus resultados. Por ejemplo, las reglas de precedencia requieren que * vaya antes de + , + vaya antes de & , y así sucesivamente.

Sin embargo, la precedencia del operador no dice nada sobre la aplicación de efectos secundarios. Aquí es donde entran en juego los puntos de secuencia (secuencia antes, secuencia después, etc.). Dicen que para que una expresión esté bien definida, la aplicación de efectos secundarios a la misma ubicación en la memoria debe estar separada por un punto de secuencia.

Esta regla está rota por i = i++ , porque tanto ++ como = aplican sus efectos secundarios a la misma variable i . Primero, ++ va, porque tiene mayor precedencia. Calcula su valor tomando el valor original de i antes del incremento. Entonces = va, porque tiene menor precedencia. Su resultado es también el valor original de i .

Lo crucial que falta aquí es una secuencia de puntos que separa los efectos secundarios de los dos operadores. Esto es lo que hace indefinido el comportamiento.