GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Die Begründung dafür, dass die Bash-Shell Sie nicht vor arithmetischem Überlauf usw. warnt?

Den arithmetischen Auswertungsmöglichkeiten der bash sind Grenzen gesetzt Hülse. Das Handbuch behandelt diesen Aspekt der Shell-Arithmetik kurz, sagt aber:

Die Auswertung erfolgt in Ganzzahlen mit fester Breite ohne Prüfung auf Überlauf,
obwohl die Division durch 0 abgefangen und als Fehler gekennzeichnet wird. Die Operatoren
und ihre Priorität, Assoziativität und Werte sind die gleichen wie in der
Sprache C.

Auf welche Ganzzahl mit fester Breite sich das bezieht, hängt wirklich von welchem ​​Datentyp ab verwendet wird (und die Einzelheiten, warum dies so ist, gehen darüber hinaus), aber der Grenzwert wird in /usr/include/limits.h ausgedrückt auf diese Weise:

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

Und wenn man das weiß, kann man diesen Sachverhalt auch so bestätigen:

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

Dies ist eine 64-Bit-Ganzzahl und dies übersetzt direkt in der Shell im Kontext der arithmetischen Auswertung:

# 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

Zwischen 2 und 2-1 erhalten Sie also negative ganze Zahlen, die Ihnen zeigen, wie weit Sie von ULONG_MAX entfernt sind. Wenn die Auswertung diese Grenze erreicht und überläuft, in welcher Reihenfolge auch immer, erhalten Sie keine Warnung und dieser Teil der Auswertung wird auf 0 zurückgesetzt, was zu einem ungewöhnlichen Verhalten mit etwas wie rechtsassoziativ führen kann Potenzierung zum Beispiel:

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
...

Mit sh -c 'command' ändert nichts, also muss ich davon ausgehen, dass dies eine normale und konforme Ausgabe ist. Jetzt, da ich denke, dass ich ein grundlegendes, aber konkretes Verständnis des arithmetischen Bereichs und der Grenze habe und was es in der Shell für die Auswertung von Ausdrücken bedeutet, dachte ich, ich könnte schnell einen Blick darauf werfen, welche Datentypen die andere Software in Linux verwendet. Ich habe etwas bash verwendet Quellen musste ich die Eingabe dieses Befehls ergänzen:

{ 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)

Es gibt mehr Ausgabe mit dem if Anweisungen und ich kann nach einem Befehl wie awk suchen Auch usw. Ich stelle fest, dass der von mir verwendete reguläre Ausdruck nichts über willkürliche Präzisionswerkzeuge, die ich habe, wie bc, erfasst und dc .

Fragen

  1. Was ist der Grund dafür, Sie nicht zu warnen (wie awk tut beim Auswerten 2^1024), wenn Ihre arithmetische Auswertung überläuft? Warum werden dem Endbenutzer die negativen ganzen Zahlen zwischen 2 und 2-1 angezeigt, wenn er etwas bewertet?
  2. Ich habe irgendwo gelesen, dass eine UNIX-Variante ULONG_MAX interaktiv ändern kann? Hat jemand davon gehört?
  3. Wenn jemand willkürlich den Wert des vorzeichenlosen Integer-Maximums in limits.h ändert , kompiliert dann bash neu , was können wir erwarten?

Verwandte:Wie führt man Integer- und Float-Berechnungen in Bash oder anderen Sprachen/Frameworks durch?

Akzeptierte Antwort:

Zwischen 2^63 und 2^64-1 erhalten Sie also negative Ganzzahlen, die Ihnen zeigen, wie weit Sie von ULONG_MAX entfernt sind.

Nein. Wie kommst du darauf? In Ihrem eigenen Beispiel ist das Maximum:

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

Wenn „Überlauf“ bedeutete „Sie erhalten negative Ganzzahlen, die Ihnen zeigen, wie weit Sie von ULONG_MAX entfernt sind“, sollten wir dann nicht -1 erhalten, wenn wir eins hinzufügen? Aber stattdessen:

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

Vielleicht meinen Sie, dass dies eine Zahl ist, die Sie zu $max hinzufügen können um eine negative Differenz zu erhalten, da:

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

Dies gilt aber tatsächlich nicht mehr:

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

Das liegt daran, dass das System das Zweierkomplement verwendet, um vorzeichenbehaftete Ganzzahlen zu implementieren. Der aus einem Überlauf resultierende Wert ist KEIN Versuch, Ihnen eine Differenz, eine negative Differenz usw. zu liefern Es ist buchstäblich das Ergebnis der Kürzung eines Werts auf eine begrenzte Anzahl von Bits, die dann als vorzeichenbehaftete Zweierkomplement-Ganzzahl interpretiert wird. Zum Beispiel der Grund $(($max + 1 + $max)) als -1 herauskommt, liegt daran, dass der höchste Wert im Zweierkomplement alle Bits gesetzt sind außer das höchste Bit (das negativ anzeigt); Diese zusammenzufügen bedeutet im Grunde, alle Bits nach links zu tragen, sodass Sie am Ende erhalten (wenn die Größe 16-Bit und nicht 64 wäre):

11111111 11111110

Das High (Vorzeichen)-Bit wird jetzt gesetzt, weil es in die Addition übernommen wurde. Wenn Sie noch eins hinzufügen (00000000 00000001), haben Sie alle Bits gesetzt , was im Zweierkomplement -1 ist.

Ich denke, das beantwortet teilweise die zweite Hälfte Ihrer ersten Frage – „Warum sind die negativen Ganzzahlen … dem Endbenutzer ausgesetzt?“. Erstens, weil das nach den Regeln der 64-Bit-Zweierkomplementzahlen der richtige Wert ist. Dies ist die herkömmliche Praxis der meisten (anderen) allgemeinen Programmiersprachen auf hoher Ebene (ich kann mir keine vorstellen, die dies nicht tut), also bash hält sich an Konventionen. Das ist auch die Antwort auf den ersten Teil der ersten Frage – „Was ist die Begründung?“:Dies ist die Norm in der Spezifikation von Programmiersprachen.

WRT die 2. Frage, ich habe noch nie von Systemen gehört, die ULONG_MAX interaktiv ändern.

Wenn jemand willkürlich den Wert des vorzeichenlosen Integer-Maximums in limits.h ändert und dann bash neu kompiliert, was können wir erwarten, dass es passieren wird?

Es würde keinen Unterschied machen, wie die Arithmetik herauskommt, da dies kein willkürlicher Wert ist, der zum Konfigurieren des Systems verwendet wird – es ist ein Bequemlichkeitswert, der eine unveränderliche Konstante speichert, die die Hardware widerspiegelt. Analog könnten Sie c neu definieren 55 Meilen pro Stunde betragen, aber die Lichtgeschwindigkeit wird immer noch 186.000 Meilen pro Sekunde betragen. c ist keine Zahl, die verwendet wird, um das Universum zu konfigurieren – es ist eine Schlussfolgerung über die Natur des Universums.

Verwandte:Python – Keine solche Datei oder Verzeichnis, aber ich kann es sehen!?

ULONG_MAX ist genau das gleiche. Sie wird basierend auf der Natur von N-Bit-Zahlen abgeleitet/berechnet. Ändern in limits.h wäre eine sehr schlechte Idee, wenn diese Konstante irgendwo verwendet wird, vorausgesetzt, sie soll die Realität des Systems darstellen .

Und Sie können die von Ihrer Hardware auferlegte Realität nicht ändern.


Linux
  1. Was ein Shell-Dotfile für Sie tun kann

  2. Anpassen der Bash-Shell

  3. Der Zweck des Schlüsselworts „do“ in Bash-For-Schleifen?

  4. Bash Echo Die Befehlszeile wird in der Befehlszeile selbst ausgeführt (nicht in einem Skript)?

  5. Vorrang der logischen Shell-Operatoren &&, ||?

Warum funktioniert das Parent Shell Here-Dokument nicht für Unterbefehle in Dash, aber Bash funktioniert?

Die for-Schleife in Shell-Skripten verstehen

Die Bash FOR-Schleife erklärt und vereinfacht

Bleiben Sie auf dem Laufenden – Beispiele für Bash-For-, While- und Until-Loops

Warum müssen Sie #!/bin/bash an den Anfang einer Skriptdatei setzen?

Variablenbereich für Bash-Shell-Skripte und Funktionen im Skript