operator c++ c++11 ternary-operator operator-precedence comma-operator

c++ - operator - obtuviste una respuesta inesperada de la expresión x? y: z



operator precedence java (6)

Debido a la precedencia del operador, la expresión se analiza así:

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2), maxx=x1;

Puedes resolver esto con:

(x1<=x2) ? (minx=x1,maxx=x2) : (minx=x2, maxx=x1);

Y en realidad no necesitas los dos primeros pares de paréntesis. También revise esta pregunta .

Aquí hay un simple fragmento de C ++:

int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy; x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1; y1<=y2 ? miny=y1,maxy=y2 : miny=y2,maxy=y1; cout<<"minx="<<minx<<"/n"; cout<<"maxx="<<maxx<<"/n"; cout<<"miny="<<miny<<"/n"; cout<<"maxy="<<maxy<<"/n";

Pensé que el resultado debería ser:

minx=10 maxx=20 miny=12 maxy=132

Pero en realidad el resultado es:

minx=10 maxx=10 miny=12 maxy=132

¿Podría alguien dar una explicación de por qué maxx no es 20? Gracias.


Debido a la precedencia del operador:

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1

Puedes arreglarlo con:

int x1=10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy; x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1); y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1); cout<<"minx="<<minx<<"/n"; cout<<"maxx="<<maxx<<"/n"; cout<<"miny="<<miny<<"/n"; cout<<"maxy="<<maxy<<"/n";


En C ++ 11, puedes usar std::tie y std::make_pair para hacer esto de forma obvia (TM)

#include <tuple> #include <utility> #include <iostream> using namespace std; int main() { int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy; tie(minx, maxx) = (x1 <= x2)? make_pair(x1, x2) : make_pair(x2, x1); tie(miny, maxy) = (y1 <= y2)? make_pair(y1, y2) : make_pair(y2, y1); cout<<"minx="<<minx<<"/n"; cout<<"maxx="<<maxx<<"/n"; cout<<"miny="<<miny<<"/n"; cout<<"maxy="<<maxy<<"/n"; }

output línea.

Esto es semánticamente equivalente a todas las otras soluciones publicadas, y con cualquier compilador de optimización decente, no tiene gastos generales en absoluto. Es sintácticamente mucho mejor porque tiene

  • un mínimo de repetición de código,
  • los 4 asignados a variables están todos en el lado izquierdo de la tarea, y
  • Las 4 variables asignadas están todas a la derecha.

Como una pequeña variación que se generaliza para encontrar punteros al elemento mínimo y máximo de las secuencias, puede usar std::minmax_element y el hecho de que las matrices sin std::minmax_element tienen funciones no miembros begin() y end() (ambas características de C ++ 11 )

#include <algorithm> #include <tuple> #include <iostream> using namespace std; int main() { int x[] = { 10, 20 }, y[] = { 132, 12 }, *minx, *miny, *maxx, *maxy; tie(minx, maxx) = minmax_element(begin(x), end(x)); tie(miny, maxy) = minmax_element(begin(y), end(y)); cout<<"minx="<<*minx<<"/n"; cout<<"maxx="<<*maxx<<"/n"; cout<<"miny="<<*miny<<"/n"; cout<<"maxy="<<*maxy<<"/n"; }

output línea.


Interesante pregunta tanto sobre la precedencia de operaciones como sobre la generación de código.

OK, la operación tiene una prioridad MUY baja (la más baja, ver tabla de referencia ) Debido a este hecho, su código es el mismo que el de las siguientes líneas:

((x1<=x2) ? minx=x1,maxx=x2 : minx=x2),maxx=x1; ((y1<=y2) ? miny=y1,maxy=y2 : miny=y2),maxy=y1;

En realidad, solo la gramática C / C ++ previene primero , del mismo comportamiento.

Otro lugar realmente peligroso en la precedencia de las operaciones de C / C ++ son las operaciones y comparación a nivel de bits. Considere sobre el siguiente fragmento:

int a = 2; int b = (a == 2|1); // Looks like check for expression result? Nope, results 1!

De cara al futuro, recomendaría volver a escribir su fragmento de esta manera manteniendo el equilibrio entre eficiencia y legibilidad:

minx = ((x1 <= x2) ? x1 : x2); maxx = ((x1 <= x2) ? x2 : x1); miny = ((y1 <= y2) ? y1 : y2); maxy = ((y1 <= y2) ? y2 : y1);

El hecho más interesante sobre este fragmento de código es que este estilo podría resultar en el código más efectivo para algunas arquitecturas como ARM debido a los indicadores de bits de condición en el conjunto de instrucciones de la CPU (la duplicación de condiciones no cuesta nada y más, el compilador apunta a los fragmentos de código correctos).


La precedencia del operador condicional es mayor que la del operador de coma, por lo que

x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1;

está entre paréntesis como

(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1;

Así, la última tarea se realiza independientemente de la condición.

Para arreglarlo, puedes

  • utilizar paréntesis:

    x1 <= x2 ? (minx = x1, maxx = x2) : (minx = x2, maxx = x1);

    (no necesita los paréntesis en la rama true , pero es IMO mejor tenerlos también).

  • utilizar dos condicionales:

    minx = x1 <= x2 ? x1 : x2; maxx = x1 <= x2 ? x2 : x1;

  • usa un if :

    if (x1 <= x2) { minx = x1; maxx = x2; } else { minx = x2; maxx = x1; }

Compilado con o sin optimizaciones, la versión if y el condicional único entre paréntesis con comas producen el mismo ensamblaje tanto en gcc (4.7.2) como en clang (3.2), es razonable esperar que también de otros compiladores. La versión con los dos condicionales produce un ensamblaje diferente, pero con optimizaciones, ambos compiladores emiten solo una instrucción cmp para eso también.

En mi opinión, la versión if es la más fácil de verificar if es correcta, así que es preferible.


Mientras que otros han explicado cuál es la causa del problema, creo que la solución "mejor" debería ser escribir el condicional con:

int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy; if (x1<=x2) { minx=x1; maxx=x2; } else { minx=x2; maxx=x1; } if (y1<=y2) { miny=y1; maxy=y2; } else { miny=y2; maxy=y1; }

Sí, es varias líneas más, pero también es más fácil de leer y borrar exactamente lo que sucede (y si necesita avanzar en el depurador, puede ver fácilmente hacia dónde se dirige).

Cualquier compilador moderno debería poder convertir cualquiera de estos en asignaciones condicionales bastante eficientes que hagan un buen trabajo al evitar las ramas (y por lo tanto, la "mala predicción de las ramas").

Preparé una pequeña prueba, que compilé usando

g++ -O2 -fno-inline -S -Wall ifs.cpp

Aquí está la fuente (tuve que hacer los parámetros para asegurar que el compilador no solo calculó el valor correcto directamente y solo mov $12,%rdx , sino que hizo una comparación y decidió con más grande):

void mine(int x1, int x2, int y1, int y2) { int minx, miny, maxx, maxy; if (x1<=x2) { minx=x1; maxx=x2; } else { minx=x2; maxx=x1; } if (y1<=y2) { miny=y1; maxy=y2; } else { miny=y2; maxy=y1; } cout<<"minx="<<minx<<"/n"; cout<<"maxx="<<maxx<<"/n"; cout<<"miny="<<miny<<"/n"; cout<<"maxy="<<maxy<<"/n"; } void original(int x1, int x2, int y1, int y2) { int minx, miny, maxx, maxy; x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1); y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1); cout<<"minx="<<minx<<"/n"; cout<<"maxx="<<maxx<<"/n"; cout<<"miny="<<miny<<"/n"; cout<<"maxy="<<maxy<<"/n"; } void romano(int x1, int x2, int y1, int y2) { int minx, miny, maxx, maxy; minx = ((x1 <= x2) ? x1 : x2); maxx = ((x1 <= x2) ? x2 : x1); miny = ((y1 <= y2) ? y1 : y2); maxy = ((y1 <= y2) ? y2 : y1); cout<<"minx="<<minx<<"/n"; cout<<"maxx="<<maxx<<"/n"; cout<<"miny="<<miny<<"/n"; cout<<"maxy="<<maxy<<"/n"; } int main() { int x1=10, x2=20, y1=132, y2=12; mine(x1, x2, y1, y2); original(x1, x2, y1, y2); romano(x1, x2, y1, y2); return 0; }

El código generado se ve así:

_Z4mineiiii: .LFB966: .cfi_startproc movq %rbx, -32(%rsp) movq %rbp, -24(%rsp) movl %ecx, %ebx movq %r12, -16(%rsp) movq %r13, -8(%rsp) movl %esi, %r12d subq $40, %rsp movl %edi, %r13d cmpl %esi, %edi movl %edx, %ebp cmovg %edi, %r12d cmovg %esi, %r13d movl $_ZSt4cout, %edi cmpl %ecx, %edx movl $.LC0, %esi cmovg %edx, %ebx cmovg %ecx, %ebp .... removed actual printout code that is quite long and unwieldy... _Z8originaliiii: movq %rbx, -32(%rsp) movq %rbp, -24(%rsp) movl %ecx, %ebx movq %r12, -16(%rsp) movq %r13, -8(%rsp) movl %esi, %r12d subq $40, %rsp movl %edi, %r13d cmpl %esi, %edi movl %edx, %ebp cmovg %edi, %r12d cmovg %esi, %r13d movl $_ZSt4cout, %edi cmpl %ecx, %edx movl $.LC0, %esi cmovg %edx, %ebx cmovg %ecx, %ebp ... print code goes here ... _Z6romanoiiii: movq %rbx, -32(%rsp) movq %rbp, -24(%rsp) movl %edx, %ebx movq %r12, -16(%rsp) movq %r13, -8(%rsp) movl %edi, %r12d subq $40, %rsp movl %esi, %r13d cmpl %esi, %edi movl %ecx, %ebp cmovle %edi, %r13d cmovle %esi, %r12d movl $_ZSt4cout, %edi cmpl %ecx, %edx movl $.LC0, %esi cmovle %edx, %ebp cmovle %ecx, %ebx ... printout code here....

Como puede ver, el mine y el original son idénticos, y el romano utiliza registros ligeramente diferentes y una forma diferente de cmov , pero de lo contrario hacen lo mismo en el mismo número de instrucciones.