c++ - variable - que es swap en java
¿Por qué a=(a+b)-(b=a) es una mala elección para intercambiar dos enteros? (10)
Me tropecé con este código para intercambiar dos enteros sin usar una variable temporal o el uso de operadores bit a bit.
int main(){
int a=2,b=3;
printf("a=%d,b=%d",a,b);
a=(a+b)-(b=a);
printf("/na=%d,b=%d",a,b);
return 0;
}
Pero creo que este código tiene un comportamiento indefinido en la declaración de intercambio a = (a+b) - (b=a);
ya que no contiene ningún punto de secuencia para determinar el orden de evaluación.
Mi pregunta es: ¿Es esta una solución aceptable para intercambiar dos enteros?
Mi pregunta es: ¿Es esta una solución aceptable para intercambiar dos enteros?
Aceptable para quien? Si me preguntas si es aceptable para mí, eso no superará ninguna revisión de código en la que estuve, créeme.
¿Por qué a = (a + b) - (b = a) es una mala elección para intercambiar dos enteros?
Por las siguientes razones:
1) Como nota, no hay ninguna garantía en C de que realmente lo haga. Podría hacer cualquier cosa.
2) Supongamos por el argumento que realmente intercambia dos enteros, como lo hace en C #. (C # garantiza que los efectos secundarios sucedan de izquierda a derecha.) ¡El código aún sería inaceptable porque no es completamente obvio cuál es su significado! El código no debe ser un montón de trucos ingeniosos. Escriba el código para la persona que viene después de usted que tiene que leerlo y entenderlo.
3) Nuevamente, supongamos que funciona. El código sigue siendo inaceptable porque esto es simplemente falso:
Me tropecé con este código para intercambiar dos enteros sin usar una variable temporal o el uso de operadores bit-wise.
Eso es simplemente falso. Este truco usa una variable temporal para almacenar el cálculo de a+b
. La variable es generada por el compilador en su nombre y no se le asigna un nombre, pero está allí. Si el objetivo es eliminar los temporales, esto lo empeora, ¡no mejora! ¿Y por qué querrías eliminar los temporales en primer lugar? ¡Son baratos!
4) Esto solo funciona para enteros. Se deben intercambiar muchas cosas que no sean enteros.
En resumen, dedique su tiempo a concentrarse en escribir código que obviamente es correcto, en lugar de tratar de inventar trucos inteligentes que empeoren las cosas.
Además de las otras respuestas, sobre comportamiento y estilo indefinidos, si escribe código simple que solo usa una variable temporal, el compilador puede rastrear los valores y no intercambiarlos en el código generado, y simplemente usar los valores intercambiados más adelante en algunos casos. No puede hacer eso con tu código. El compilador generalmente es mejor que usted en micro optimizaciones.
Por lo tanto, es probable que su código sea más lento, más difícil de entender y, probablemente, también un comportamiento indefinido poco confiable.
Además de los problemas invocados por los otros usuarios, este tipo de intercambio tiene otro problema: el dominio .
¿Qué pasa si a+b
va más allá de su límite? Digamos que trabajamos con números del 0
al 100
. 80+ 60
saldrían fuera de rango.
El problema es que de acuerdo con el estándar C ++
Excepto donde se indique, las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales no son secuenciadas.
Entonces esta expresión
a=(a+b)-(b=a);
tiene un comportamiento indefinido.
Hay al menos dos problemas con a=(a+b)-(b=a)
.
Uno se menciona a sí mismo: la falta de puntos de secuencia significa que el comportamiento no está definido. Como tal, cualquier cosa podría pasar. Por ejemplo, no hay garantía de que se evalúe primero: a+b
o b=a
. El compilador puede elegir generar código para la tarea primero o hacer algo completamente diferente.
Otro problema es el hecho de que el exceso de aritmética firmada es un comportamiento indefinido. Si a+b
desborda no hay garantía de los resultados; incluso una excepción podría ser lanzada.
La declaración:
a=(a+b)-(b=a);
invoca comportamiento indefinido Se viola el segundo en el párrafo citado:
(C99, 6.5p2) "Entre el punto de secuencia anterior y el siguiente, un objeto tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión. Además, el valor anterior será de solo lectura para determinar el valor que se almacenará " .
No. Esto no es aceptable. Este código invoca un comportamiento indefinido . Esto se debe a que la operación en b
no está definida. En la expresión
a=(a+b)-(b=a);
no es seguro si b
se modifica primero o si su valor se usa en la expresión ( a+b
) debido a la falta del punto de secuencia .
Ver qué syas estándar:
C11: 6.5 Expresiones:
Si un efecto secundario en un objeto escalar no se está secuenciando en relación con un efecto secundario diferente en el mismo objeto escalar o un cálculo de valor que utiliza el valor del mismo objeto escalar , el comportamiento no está definido. Si hay múltiples ordenamientos admisibles de las subexpresiones de una expresión, el comportamiento no está definido si dicho efecto secundario no secuenciado ocurre en cualquiera de los pedidos.84) 1 .
Lea C-faq-3.8 y esta answer para una explicación más detallada del punto de secuencia y el comportamiento indefinido.
1. El énfasis es mío
Puede usar el algoritmo de intercambio XOR para evitar problemas de desbordamiento y aún así tener un solo trazo.
Pero, dado que tienes una etiqueta de c++
, prefiero un simple std::swap(a, b)
para que sea más fácil de leer.
Se publicó una pregunta en 2010 con el mismo ejemplo exacto.
a = (a+b) - (b=a);
Steve Jessop advierte contra eso:
El comportamiento de ese código no está definido, por cierto. Tanto a como b se leen y escriben sin un punto de secuencia intermedio. Para empezar, el compilador estaría en su derecho de evaluar b = a antes de evaluar a + b.
Aquí hay una explicación de una pregunta publicada en 2012 . Tenga en cuenta que la muestra no es exactamente la misma debido a la falta de paréntesis, pero la respuesta aún es relevante.
En C ++, las subexpresiones en expresiones aritméticas no tienen orden temporal.
a = x + y;
¿Se evalúa x primero, o y? El compilador puede elegir cualquiera, o puede elegir algo completamente diferente. El orden de evaluación no es lo mismo que la precedencia del operador: la precedencia del operador está estrictamente definida, y el orden de evaluación solo se define por la granularidad de que su programa tenga puntos de secuencia.
De hecho, en algunas arquitecturas es posible emitir código que evalúa tanto x como y al mismo tiempo, por ejemplo, arquitecturas VLIW.
Ahora para C11 cotizaciones estándar de N1570:
Anexo J.1 / 1
Es un comportamiento no especificado cuando:
- El orden en que se evalúan las subexpresiones y el orden en que se producen los efectos secundarios, excepto como se especifica para la función-llamada
()
,&&
,||
,? :
? :
, y operadores de coma (6.5).- El orden en que se evalúan los operandos de un operador de asignación (6.5.16).
Anexo J.2 / 1
Es un comportamiento indefinido cuando:
- Un efecto secundario en un objeto escalar no se secuencia en relación con un efecto secundario diferente en el mismo objeto escalar o un cálculo de valor que utiliza el valor del mismo objeto escalar (6.5).
6.5 / 1
Una expresión es una secuencia de operadores y operandos que especifica el cálculo de un valor, o que designa un objeto o una función, o que genera efectos secundarios, o que realiza una combinación de los mismos. Los cálculos de valor de los operandos de un operador se ordenan antes del cálculo del valor del resultado del operador.
6.5 / 2
Si un efecto secundario en un objeto escalar no se está secuenciando en relación con un efecto secundario diferente en el mismo objeto escalar o un cálculo de valor que utiliza el valor del mismo objeto escalar, el comportamiento no está definido. Si hay múltiples ordenamientos permitidos de las subexpresiones de una expresión, el comportamiento no está definido si dicho efecto secundario no secuenciado ocurre en cualquiera de los pedidos.84)
6.5 / 3
La agrupación de operadores y operandos se indica mediante la sintaxis.85) Excepto como se especifica más adelante, los efectos secundarios y los cálculos de valor de las subexpresiones no se han secuenciado.86)
No deberías confiar en un comportamiento indefinido.
Algunas alternativas: en C ++ puedes usar
std::swap(a, b);
XOR-swap:
a = a^b;
b = a^b;
a = a^b;
Si usa gcc y -Wall
el compilador ya lo advierte
ac: 3: 26: advertencia: la operación en ''b'' puede estar indefinida [-Wsequence-point]
Si usar tal construcción es discutible desde un punto de desempeño también. Cuando miras
void swap1(int *a, int *b)
{
*a = (*a + *b) - (*b = *a);
}
void swap2(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
y examinar el código de ensamblaje
swap1:
.LFB0:
.cfi_startproc
movl (%rdi), %edx
movl (%rsi), %eax
movl %edx, (%rsi)
movl %eax, (%rdi)
ret
.cfi_endproc
swap2:
.LFB1:
.cfi_startproc
movl (%rdi), %eax
movl (%rsi), %edx
movl %edx, (%rdi)
movl %eax, (%rsi)
ret
.cfi_endproc
no puede ver ningún beneficio por ofuscar el código.
Mirando el código C ++ (g ++), que básicamente hace lo mismo, pero toma move
en la cuenta
#include <algorithm>
void swap3(int *a, int *b)
{
std::swap(*a, *b);
}
da salida de ensamblaje idéntica
_Z5swap3PiS_:
.LFB417:
.cfi_startproc
movl (%rdi), %eax
movl (%rsi), %edx
movl %edx, (%rdi)
movl %eax, (%rsi)
ret
.cfi_endproc
Teniendo en cuenta la advertencia de gcc y sin ver ninguna ganancia técnica, diría que sigan las técnicas estándar. Si esto se convierte en un cuello de botella, aún puede investigar, cómo mejorar o evitar este pequeño fragmento de código.