GNU/Linux >> Tutoriels Linux >  >> Linux

Pourquoi le shell Bash ne vous avertit-il pas d'un débordement arithmétique, etc. ?

Des limites sont définies pour les capacités d'évaluation arithmétique du bash coquille. Le manuel est succinct sur cet aspect de l'arithmétique shell mais indique :

L'évaluation est effectuée en nombres entiers à largeur fixe sans contrôle de dépassement,
bien que la division par 0 soit interceptée et signalée comme une erreur. Les opérateurs
et leur priorité, leur associativité et leurs valeurs sont les mêmes que dans le
langage C.

L'entier à largeur fixe auquel cela fait référence concerne en réalité le type de données est utilisé (et les détails de pourquoi cela va au-delà) mais la valeur limite est exprimée dans /usr/include/limits.h de cette façon :

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

Et une fois que vous savez cela, vous pouvez confirmer cet état de fait comme suit :

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

C'est un entier 64 bits et cela se traduit directement dans le shell dans le cadre de l'évaluation arithmétique :

# 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

Ainsi, entre 2 et 2-1, vous obtenez des entiers négatifs vous indiquant à quelle distance vous vous trouvez de ULONG_MAX. Lorsque l'évaluation atteint cette limite et déborde, quel que soit l'ordre, vous ne recevez aucun avertissement et cette partie de l'évaluation est réinitialisée à 0, ce qui peut entraîner un comportement inhabituel avec quelque chose comme right-associative exponentiation par exemple :

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

Utilisation de sh -c 'command' ne change rien, je dois donc supposer qu'il s'agit d'une sortie normale et conforme. Maintenant que je pense avoir une compréhension basique mais concrète de la plage et de la limite arithmétiques et de ce que cela signifie dans le shell pour l'évaluation des expressions, j'ai pensé que je pourrais rapidement jeter un coup d'œil sur les types de données utilisés par les autres logiciels sous Linux. J'ai utilisé du bash sources, j'ai dû compléter l'entrée de cette commande :

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

Il y a plus de sortie avec le if déclarations et je peux rechercher une commande comme awk aussi etc. Je remarque que l'expression régulière que j'ai utilisée n'attrape rien sur les outils de précision arbitraires que j'ai tels que bc et dc .

Questions

  1. Quelle est la raison pour ne pas vous avertir (comme awk fait lors de l'évaluation de 2^1024) lorsque votre évaluation arithmétique déborde ? Pourquoi les entiers négatifs entre 2 et 2-1 sont-ils exposés à l'utilisateur final lorsqu'il évalue quelque chose ?
  2. J'ai lu quelque part qu'une certaine version d'UNIX peut modifier interactivement ULONG_MAX ? Quelqu'un en a-t-il entendu parler ?
  3. Si quelqu'un modifie arbitrairement la valeur de l'entier non signé maximum dans limits.h , puis recompile bash , à quoi peut-on s'attendre ?

En relation :Comment effectuer des calculs entiers et flottants, en bash ou dans d'autres langages/cadres ?

Réponse acceptée :

Ainsi, entre 2^63 et 2^64-1, vous obtenez des nombres entiers négatifs vous indiquant à quelle distance vous vous trouvez de ULONG_MAX.

Non. Comment pensez-vous cela ? Selon votre propre exemple, le maximum est :

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

Si "débordement" signifiait "vous obtenez des nombres entiers négatifs vous indiquant à quelle distance vous vous trouvez de ULONG_MAX", alors si nous en ajoutons un, ne devrions-nous pas obtenir -1 ? Mais à la place :

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

Vous voulez peut-être dire qu'il s'agit d'un nombre que vous pouvez ajouter à $max pour obtenir une différence négative, puisque :

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

Mais cela n'est en fait pas toujours vrai :

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

C'est parce que le système utilise le complément à deux pour implémenter des entiers signés. La valeur résultant d'un débordement n'est PAS une tentative de vous fournir une différence, une différence négative, etc. C'est littéralement le résultat de la troncature d'une valeur à un nombre limité de bits, puis de son interprétation comme un entier signé en complément à deux. Par exemple, la raison $(($max + 1 + $max)) sort comme -1 parce que la valeur la plus élevée en complément à deux est tous les bits définis sauf le bit le plus élevé (qui indique négatif) ; les ajouter ensemble signifie essentiellement transporter tous les bits vers la gauche pour que vous vous retrouviez avec (si la taille était de 16 bits et non de 64) :

11111111 11111110

Le bit haut (signe) est maintenant défini car il a été reporté dans l'addition. Si vous en ajoutez un de plus (00000000 00000001) à cela, vous avez alors tous les bits définis , qui en complément à deux vaut -1.

Je pense que cela répond partiellement à la seconde moitié de votre première question - "Pourquoi les entiers négatifs… sont-ils exposés à l'utilisateur final ?". Premièrement, parce que c'est la valeur correcte selon les règles des nombres de complément à deux 64 bits. C'est la pratique conventionnelle de la plupart des (autres) langages de programmation de haut niveau à usage général (je ne peux pas penser à un qui ne le fasse pas), donc bash est conforme à la convention. C'est aussi la réponse à la première partie de la première question - "Quelle est la raison ?" :c'est la norme dans la spécification des langages de programmation.

WRT la 2ème question, je n'ai pas entendu parler de systèmes qui modifient interactivement ULONG_MAX.

Si quelqu'un modifie arbitrairement la valeur maximale de l'entier non signé dans limits.h, puis recompile bash, à quoi pouvons-nous nous attendre ?

Cela ne ferait aucune différence dans la façon dont l'arithmétique sort, car ce n'est pas une valeur arbitraire utilisée pour configurer le système - c'est une valeur de commodité qui stocke une constante immuable reflétant le matériel. Par analogie, vous pourriez redéfinir c à 55 mph, mais la vitesse de la lumière sera toujours de 186 000 miles par seconde. c n'est pas un nombre utilisé pour configurer l'univers - c'est une déduction sur la nature de l'univers.

Connexe :Python - Aucun fichier ou répertoire de ce type, mais je peux le voir ! ?

ULONG_MAX est exactement le même. Il est déduit/calculé en fonction de la nature des nombres à N bits. Modification dans limits.h serait une très mauvaise idée si cette constante était utilisée quelque part en supposant qu'elle est censée représenter la réalité du système .

Et vous ne pouvez pas changer la réalité imposée par votre matériel.


Linux
  1. Qu'est-ce qu'un dotfile shell peut faire pour vous

  2. Le but du mot-clé "do" dans Bash For Loops ?

  3. Pourquoi avez-vous besoin de mettre #!/bin/bash au début d'un fichier de script ?

  4. Bash Shell Script - Recherchez un indicateur et saisissez sa valeur

  5. Portée variable pour les scripts shell bash et les fonctions dans le script

Comprendre la boucle for dans les scripts shell

La boucle Bash FOR expliquée et simplifiée

Vous tenir au courant - Exemples de boucle Bash For, While, Until

Mode IDE / Emacs pour les scripts Shell dans Bash/Sh, etc.

Quelles sont les règles pour les identifiants valides (par exemple, fonctions, vars, etc.) dans Bash ?

À quoi sert $# dans Bash