GNU/Linux >> Linux Esercitazione >  >> Linux

Il motivo per cui Bash Shell non ti avverte di overflow aritmetico ecc.?

Ci sono limiti fissati per le capacità di valutazione aritmetica di bash guscio. Il manuale è conciso su questo aspetto dell'aritmetica della shell ma afferma:

La valutazione viene eseguita in numeri interi a larghezza fissa senza alcun controllo per l'overflow,
sebbene la divisione per 0 venga intercettata e contrassegnata come errore. Gli operatori
e la loro precedenza, associatività e valori sono gli stessi del linguaggio
C.

L'intero a larghezza fissa a cui si riferisce è in realtà su quale tipo di dati viene utilizzato (e le specifiche del perché questo è oltre questo) ma il valore limite è espresso in /usr/include/limits.h in questo modo:

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

E una volta che lo sai, puoi confermare questo stato di fatto in questo modo:

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

Questo è un numero intero a 64 bit e questo si traduce direttamente nella shell nel contesto della valutazione aritmetica:

# 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

Quindi tra 2 e 2-1, ottieni numeri interi negativi che ti mostrano quanto sei lontano da ULONG_MAX. Quando la valutazione raggiunge quel limite e va in overflow, indipendentemente dall'ordine, non si riceve alcun avviso e quella parte della valutazione viene reimpostata su 0, il che potrebbe produrre un comportamento insolito con qualcosa come associativo destro esponenziazione per esempio:

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' non cambia nulla, quindi devo presumere che questo sia un output normale e conforme. Ora che penso di avere una comprensione di base ma concreta dell'intervallo e del limite aritmetici e di cosa significa nella shell per la valutazione delle espressioni, ho pensato di poter dare un'occhiata rapidamente ai tipi di dati utilizzati dagli altri software in Linux. Ho usato un po' di bash sorgenti ho dovuto integrare l'input di questo 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)

C'è più output con if istruzioni e posso cercare un comando come awk anche ecc. Ho notato che l'espressione regolare che ho usato non cattura nulla sugli strumenti di precisione arbitrari che ho come bc e dc .

Domande

  1. Qual ​​è il motivo per non avvisarti (come awk fa quando valuti 2^1024) quando la tua valutazione aritmetica va in overflow? Perché i numeri interi negativi compresi tra 2 e 2-1 sono esposti all'utente finale quando sta valutando qualcosa?
  2. Ho letto da qualche parte che un po' di UNIX può cambiare interattivamente ULONG_MAX? Qualcuno ne ha sentito parlare?
  3. Se qualcuno modifica arbitrariamente il valore del massimo intero senza segno in limits.h , quindi ricompila bash , cosa possiamo aspettarci che accada?

Correlati:come eseguire calcoli interi e float, in bash o in altri linguaggi/framework?

Risposta accettata:

Quindi tra 2^63 e 2^64-1, ottieni numeri interi negativi che ti mostrano quanto sei lontano da ULONG_MAX.

No. Come lo capisci? Secondo il tuo esempio, il massimo è:

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

Se "overflow" significa "ottieni numeri interi negativi che ti mostrano quanto sei lontano da ULONG_MAX", quindi se ne aggiungiamo uno, non dovremmo ottenere -1? Ma invece:

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

Forse vuoi dire che questo è un numero che puoi aggiungere a $max per ottenere una differenza negativa, poiché:

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

Ma questo in realtà non continua a valere:

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

Questo perché il sistema utilizza il complemento a due per implementare gli interi con segno. Il valore risultante da un overflow NON è un tentativo di fornire una differenza, una differenza negativa, ecc. È letteralmente il risultato del troncare un valore a un numero limitato di bit, quindi interpretarlo come un intero con segno in complemento a due. Ad esempio, il motivo $(($max + 1 + $max)) risulta -1 perché il valore più alto nel complemento a due è tutti i bit impostati tranne il bit più alto (che indica negativo); sommarli insieme significa sostanzialmente portare tutti i bit a sinistra in modo da ottenere (se la dimensione fosse 16 bit e non 64):

11111111 11111110

Il bit (segno) alto è ora impostato perché è stato riportato nell'addizione. Se ne aggiungi un altro (00000000 00000001), hai tutti i bit impostati , che nel complemento a due è -1.

Penso che questo risponda in parte alla seconda metà della tua prima domanda:"Perché i numeri interi negativi... vengono esposti all'utente finale?". Primo, perché questo è il valore corretto secondo le regole dei numeri in complemento a due a 64 bit. Questa è la pratica convenzionale della maggior parte (altri) linguaggi di programmazione generici di alto livello (non riesco a pensare a uno che non lo faccia), quindi bash sta aderendo alla convenzione. Che è anche la risposta alla prima parte della prima domanda:"Qual è la logica?":questa è la norma nella specifica dei linguaggi di programmazione.

WRT la seconda domanda, non ho sentito parlare di sistemi che cambiano interattivamente ULONG_MAX.

Se qualcuno modifica arbitrariamente il valore del massimo intero senza segno in limit.h, quindi ricompila bash, cosa possiamo aspettarci che accada?

Non farebbe alcuna differenza su come esce l'aritmetica, perché questo non è un valore arbitrario utilizzato per configurare il sistema:è un valore di convenienza che memorizza una costante immutabile che riflette l'hardware. Per analogia, potresti ridefinire c essere 55 mph, ma la velocità della luce sarà comunque di 186.000 miglia al secondo. c non è un numero utilizzato per configurare l'universo, è una deduzione sulla natura dell'universo.

Relazionato:Python – Nessun file o directory di questo tipo ma posso vederlo!?

ULONG_MAX è esattamente lo stesso. Viene dedotto/calcolato in base alla natura dei numeri a N bit. Cambiare in limits.h sarebbe una pessima idea se quella costante fosse usata da qualche parte supponendo che rappresenti la realtà del sistema .

E non puoi cambiare la realtà imposta dal tuo hardware.


Linux
  1. Modalità IDE/Emacs per lo scripting Shell in Bash/Sh, ecc

  2. Bash Shell Script:verifica la presenza di un flag e acquisisci il suo valore

  3. Ambito variabile per gli script e le funzioni della shell bash nello script

  4. Quali sono le regole per gli identificatori validi (ad esempio funzioni, vars, ecc.) in Bash?

  5. Qual è l'uso di $# in Bash

Cosa può fare per te un file dot di shell

Lo scopo della parola chiave "do" in Bash For Loops?

Bash Echo La riga di comando eseguita sulla riga di comando stessa (non in uno script)?

Perché il documento Parent Shell Here non funziona per il sottocomando in Dash ma Bash funziona?

Comprendere il ciclo for negli script della shell

Il ciclo Bash FOR spiegato e semplificato