unarios relacionales prioridad precedencia operadores operador expresiones evaluación condicional aritmeticos c++ c precedence associativity ansi-c

c++ - relacionales - ¿Quién define la precedencia del operador C y la asociatividad?



precedencia de operadores (5)

Introducción

En cada libro de texto en C / C ++, encontrará una tabla de precedencia y asociación de operadores como la siguiente:

http://en.cppreference.com/w/cpp/language/operator_precedence

Una de las preguntas en StackOverflow hizo algo como esto:

¿Qué orden ejecutan las siguientes funciones?

f1() * f2() + f3();
f1() + f2() * f3();

Refiriéndome al gráfico anterior, respondí con confianza que las funciones tienen asociatividad de izquierda a derecha, por lo que en las declaraciones anteriores se evalúan así en ambos casos:

f1 () -> f2 () -> f3 ()

Después de evaluar las funciones, finaliza la evaluación de esta manera:

(a1 * a2) + a3
a1 + (a2 * a3)

Para mi sorpresa, muchas personas me dijeron que estaba totalmente equivocado. Decidido a probar que estaban equivocados, decidí recurrir al estándar ANSI C11. Me sorprendió una vez más descubrir que se menciona muy poco sobre la precedencia y la asociatividad del operador.

Preguntas

  1. Si mi creencia de que las funciones siempre se evalúan de izquierda a derecha es errónea, ¿qué significa realmente la tabla que se refiere a la precedencia y asociatividad de la función?
  2. ¿Quién define la precedencia y la asociatividad del operador si no es ANSI? Si es ANSI quien hace la definición, ¿por qué se menciona poco sobre la precedencia y la asociatividad del operador? ¿Se deducen la precedencia y la asociatividad del operador del estándar ANSI C o se define en Matemáticas?

La asociatividad de izquierda a derecha significa que f() - g() - h() significa (f() - g()) - h() , nada más. Supongamos que f devuelve 1 . Supongamos que g devuelve 2 . Supongamos que h devuelve 3 . La asociatividad de izquierda a derecha significa que el resultado es (1 - 2) - 3 , o -4 : a un compilador todavía se le permite llamar primero g y h , que no tiene nada que ver con la asociatividad, pero no está permitido dar una Resultado de 1 - (2 - 3) , que sería algo completamente diferente.


La precedencia de los operadores en el Estándar C se indica mediante la sintaxis.

(C99, 6.5p3) "La agrupación de operadores y operandos se indica mediante la sintaxis. 74)"

74) "La sintaxis especifica la prioridad de los operadores en la evaluación de una expresión"

La justificación de C99 también dice

"Las reglas de precedencia están codificadas en las reglas sintácticas para cada operador".

y

"Las reglas de asociatividad están codificadas de manera similar en las reglas sintácticas".

También tenga en cuenta que la asociatividad no tiene nada que ver con el orden de evaluación. En:

f1 () * f2 () + f3 ()

Las llamadas a funciones se evalúan en cualquier orden. Las reglas sintácticas de C dicen que f1() * f2() + f3() significa (f1() * f2()) + f3() pero el orden de evaluación de los operandos en la expresión no está especificado.


La precedencia del operador se define en el estándar apropiado. Los estándares para C y C ++ son la única definición verdadera de lo que son exactamente C y C ++. Así que si miras de cerca, los detalles están ahí. De hecho, los detalles están en la gramática del lenguaje. Por ejemplo, eche un vistazo a la regla de producción gramatical para + y - en C ++ (colectivamente, expresiones aditivas ):

additive-expression: multiplicative-expression additive-expression + multiplicative-expression additive-expression - multiplicative-expression

Como puede ver, una expresión multiplicativa es una subclase de una expresión aditiva . Esto significa que si tienes algo como x + y * z , la expresión y * z es una subexpresión de x + y * z . Esto define la precedencia entre estos dos operadores.

También podemos ver que el operando izquierdo de una expresión aditiva se expande a otra expresión aditiva , lo que significa que con x + y + z , x + y es una subexpresión de la misma. Esto define la asociatividad .

La asociatividad determina cómo se agruparán los usos adyacentes del mismo operador. Por ejemplo, + es asociativa de izquierda a derecha, lo que significa que x + y + z se agruparán así: (x + y) + z .

No confundas esto con el orden de evaluación . No hay absolutamente ninguna razón para que el valor de z no pueda calcularse antes de que x + y sea. Lo que importa es que es x + y que se calcula y no y + z .

Para el operador de llamada a función, la asociatividad de izquierda a derecha significa que f()() (lo que podría suceder si f devolviera un puntero a función, por ejemplo) se agrupa de la siguiente forma: otra dirección no tendría ningún sentido).

Ahora consideremos el ejemplo que estabas viendo:

f1() + f2() * f3()

El operador * tiene mayor prioridad que el operador + , por lo que las expresiones se agrupan así:

f1() + (f2() * f3())

Ni siquiera tenemos que considerar la asociatividad aquí, porque no tenemos ninguno de los mismos operadores adyacentes entre sí.

Sin embargo, la evaluación de las funciones de las expresiones de llamada no tiene ninguna secuencia. No hay razón para que no se pueda llamar primero a f3 , luego a f1 y luego a f2 . El único requisito en este caso es que los operandos de un operador se evalúen antes que el operador. Entonces, eso significaría que f2 y f3 deben llamarse antes de que se evalúe el * y que el * debe evaluar y que se debe llamar f1 antes de que se evalúe el + .

Sin embargo, algunos operadores imponen una secuencia en la evaluación de sus operandos. Por ejemplo, en x || y x || y , x siempre se evalúa antes de y . Esto permite el cortocircuito, donde y no necesita evaluarse si se sabe que x ya es true .

El orden de evaluación se definió previamente en C y C ++ con el uso de puntos de secuencia , y ambos han cambiado la terminología para definir las cosas en términos de una secuencia secuenciada antes de la relación. Para obtener más información, consulte Comportamiento indefinido y puntos de secuencia .


La precedencia y la asociatividad se definen en el estándar, y deciden cómo construir el árbol de sintaxis. La precedencia funciona por tipo de operador ( 1+2*3 es 1+(2*3) y no (1+2)*3 ) y la asociatividad funciona por posición del operador ( 1+2+3 es (1+2)+3 y No 1+(2+3) ).

El orden de evaluación es diferente (no define cómo construir el árbol de sintaxis), sino cómo evaluate los nodos de los operadores en el árbol de sintaxis. El orden de evaluación se define como no definido, nunca puede confiar en él porque los compiladores son libres de elegir cualquier orden que consideren adecuado. Esto se hace para que los compiladores puedan intentar optimizar el código. La idea es que los programadores escriban un código que no debería verse afectado por el orden de evaluación y producen los mismos resultados sin importar el orden.


Una forma de pensar acerca de la precedencia y la asociatividad es imaginar que el lenguaje solo permite declaraciones que contienen una asignación y un operador, en lugar de múltiples operadores. Así que una declaración como:

a = f1() * f2() + f3();

no se permitiría, ya que tiene 5 operadores: 3 llamadas de función, multiplicación y suma. En este lenguaje simplificado, deberías asignar todo a los temporales y luego combinarlos:

temp1 = f1(); temp2 = f2(); temp3 = temp1 * temp2; temp4 = f3(); a = temp3 + temp4;

La asociatividad y la precedencia especifican que las dos últimas declaraciones deben realizarse en ese orden, ya que la multiplicación tiene mayor precedencia que la suma. Pero no especifica el orden relativo de las primeras 3 declaraciones; Sería igual de válido hacer:

temp4 = f3(); temp2 = f2(); temp1 = f1(); temp3 = temp1 * temp2; a = temp3 + temp4;

sftrabbit dio un ejemplo donde la asociatividad de operadores de llamada de función es relevante:

a = f()();

Al simplificarlo como anteriormente, esto se convierte en:

temp = f(); a = temp();