GNU/Linux >> Tutoriales Linux >  >> Linux

¿La razón por la que Bash Shell no le advierte sobre el desbordamiento aritmético, etc.?

Hay límites establecidos para las capacidades de evaluación aritmética del bash cáscara. El manual es sucinto sobre este aspecto de la aritmética de shell, pero establece:

La evaluación se realiza en números enteros de ancho fijo sin verificación de desbordamiento,
aunque la división por 0 queda atrapada y marcada como un error. Los operadores
y su precedencia, asociatividad y valores son los mismos que en el
lenguaje C.

A qué entero de ancho fijo se refiere realmente se trata de qué tipo de datos se utiliza (y los detalles de por qué esto es más allá de esto) pero el valor límite se expresa en /usr/include/limits.h de esta manera:

#  if __WORDSIZE == 64
#   define ULONG_MAX     18446744073709551615UL
#  ifdef __USE_ISOC99
#  define LLONG_MAX       9223372036854775807LL
#  define ULLONG_MAX    18446744073709551615ULL

Y una vez que sepa eso, puede confirmar este estado de hecho así:

# getconf -a | grep 'long'
LONG_BIT                           64
ULONG_MAX                          18446744073709551615

Este es un entero de 64 bits y esto se traduce directamente en el shell en el contexto de la evaluación aritmética:

# echo $(((2**63)-1)); echo $((2**63)); echo $(((2**63)+1)); echo $((2**64))
9223372036854775807        //the practical usable limit for your everyday use
-9223372036854775808       //you're that much "away" from 2^64
-9223372036854775807     
0
# echo $((9223372036854775808+9223372036854775807))
-1

Entonces, entre 2 y 2-1, obtiene números enteros negativos que le muestran qué tan lejos está de ULONG_MAX. Cuando la evaluación alcanza ese límite y se desborda, en el orden que sea, no recibe ninguna advertencia y esa parte de la evaluación se restablece a 0, lo que puede generar un comportamiento inusual con algo como asociación derecha exponenciación por ejemplo:

echo $((6**6**6))                      0   // 6^46656 overflows to 0
echo $((6**6**6**6))                   1   // 6^(6^46656) = 6^0 = 1
echo $((6**6**6**6**6))                6   // 6^(6(6^46656)) = 6^(6^0) = 6^1
echo $((6**6**6**6**6**6))         46656   // 6^(6^(6^(6^46656))) = 6^6
echo $((6**6**6**6**6**6**6))          0   // = 6^6^6^1 = 0
...

Usando sh -c 'command' no cambia nada, así que debo suponer que se trata de un resultado normal y compatible. Ahora que creo que tengo una comprensión básica pero concreta del rango y límite aritmético y lo que significa en el shell para la evaluación de expresiones, pensé que podría echar un vistazo rápidamente a los tipos de datos que usa el otro software en Linux. Usé algo de bash fuentes tuve que complementar la entrada de este comando:

{ shopt -s globstar; for i in /path/to/source_bash-4.2/include/**/*.h /usr/include/**/*.h; do grep -HE 'b(([UL])|(UL)|())LONG|bFLOAT|bDOUBLE|bINT' $i; done; } | grep -iE 'bash.*max'

bash-4.2/include/typemax.h:#    define LLONG_MAX   TYPE_MAXIMUM(long long int)
bash-4.2/include/typemax.h:#    define ULLONG_MAX  TYPE_MAXIMUM(unsigned long long int)
bash-4.2/include/typemax.h:#    define INT_MAX     TYPE_MAXIMUM(int)

Hay más resultados con if declaraciones y puedo buscar un comando como awk también, etc. Me doy cuenta de que la expresión regular que utilicé no capta nada sobre las herramientas de precisión arbitrarias que tengo, como bc y dc .

Preguntas

  1. ¿Cuál es la razón para no advertirle (como awk hace al evaluar 2^1024) cuando su evaluación aritmética se desborda? ¿Por qué los números enteros negativos entre 2 y 2-1 están expuestos al usuario final cuando está evaluando algo?
  2. He leído en alguna parte que algún tipo de UNIX puede cambiar ULONG_MAX de forma interactiva. ¿Alguien ha oído hablar de esto?
  3. Si alguien cambia arbitrariamente el valor del número máximo de enteros sin signo en limits.h , luego vuelve a compilar bash , ¿qué podemos esperar que suceda?

Relacionado:¿Cómo hacer cálculos de números enteros y flotantes, en bash u otros lenguajes/marcos?

Respuesta aceptada:

Entonces, entre 2^63 y 2^64-1, obtienes números enteros negativos que te muestran qué tan lejos estás de ULONG_MAX.

No. ¿Cómo te imaginas eso? Según su propio ejemplo, el máximo es:

> max=$((2**63 - 1)); echo $max
9223372036854775807

Si "desbordamiento" significa "obtienes números enteros negativos que te muestran qué tan lejos estás de ULONG_MAX", entonces si le sumamos uno, ¿no deberíamos obtener -1? Pero en cambio:

> echo $(($max + 1))
-9223372036854775808

Tal vez quiera decir que este es un número que puede agregar a $max para obtener una diferencia negativa, ya que:

> echo $(($max + 1 + $max))
-1

Pero esto, de hecho, no sigue siendo cierto:

> echo $(($max + 2 + $max))
0

Esto se debe a que el sistema usa complemento a dos para implementar enteros con signo. El valor resultante de un desbordamiento NO es un intento de proporcionarle una diferencia, una diferencia negativa, etc. Es literalmente el resultado de truncar un valor a un número limitado de bits, y luego interpretarlo como un entero con signo en complemento a dos. Por ejemplo, el motivo $(($max + 1 + $max)) sale como -1 se debe a que el valor más alto en el complemento a dos son todos los bits establecidos excepto el bit más alto (que indica negativo); sumarlos básicamente significa llevar todos los bits a la izquierda para terminar con (si el tamaño fuera de 16 bits y no de 64):

11111111 11111110

El bit alto (signo) ahora está configurado porque se transfirió en la suma. Si agrega uno más (00000000 00000001) a eso, entonces tiene todos los bits configurados , que en complemento a dos es -1.

Creo que eso responde parcialmente a la segunda mitad de su primera pregunta:"¿Por qué los números enteros negativos... están expuestos al usuario final?". Primero, porque ese es el valor correcto según las reglas de los números en complemento a dos de 64 bits. Esta es la práctica convencional de la mayoría de los (otros) lenguajes de programación de alto nivel de propósito general (no puedo pensar en uno que no haga esto), así que bash se adhiere a la convención. Cuál es también la respuesta a la primera parte de la primera pregunta:"¿Cuál es la razón?":esta es la norma en la especificación de lenguajes de programación.

WRT la segunda pregunta, no he oído hablar de sistemas que cambien interactivamente ULONG_MAX.

Si alguien cambia arbitrariamente el valor del máximo de enteros sin signo en limites.h, luego vuelve a compilar bash, ¿qué podemos esperar que suceda?

No haría ninguna diferencia en cómo resulta la aritmética, porque este no es un valor arbitrario que se usa para configurar el sistema, es un valor de conveniencia que almacena una constante inmutable que refleja el hardware. Por analogía, podría redefinir c será de 55 mph, pero la velocidad de la luz seguirá siendo de 186,000 millas por segundo. c no es un número usado para configurar el universo, es una deducción sobre la naturaleza del universo.

Relacionado:Python:¿no existe tal archivo o directorio, pero puedo verlo?

ULONG_MAX es exactamente lo mismo. Se deduce/calcula en función de la naturaleza de los números de N bits. Cambiándolo en limits.h Sería una muy mala idea si esa constante se usa en algún lugar asumiendo que se supone que representa la realidad del sistema .

Y no puedes cambiar la realidad que impone tu hardware.


Linux
  1. Lo que un archivo de puntos de shell puede hacer por usted

  2. ¿Cuál es el propósito de la palabra clave "do" en Bash For Loops?

  3. Bash Echo ¿La línea de comando se ejecutó en la propia línea de comando (no en un script)?

  4. Modo IDE/Emacs para secuencias de comandos de Shell en Bash/Sh, etc.

  5. ¿Por qué necesita poner #!/bin/bash al comienzo de un archivo de script?

Comprender el bucle for en los scripts de Shell

El Bash FOR Loop explicado y simplificado

Bash Shell Script:busque una bandera y tome su valor

Alcance variable para scripts y funciones bash shell en el script

¿Cuáles son las reglas para los identificadores válidos (por ejemplo, funciones, vars, etc.) en Bash?

¿Cuál es el uso de $# en Bash?