GNU/Linux >> Znalost Linux >  >> Linux

Odůvodnění Bash Shell vás nevaruje před aritmetickým přetečením atd.?

Pro aritmetické vyhodnocovací schopnosti bash jsou nastaveny limity skořápka. Manuál je o tomto aspektu aritmetiky shellu stručný, ale uvádí:

Vyhodnocení se provádí v celých číslech s pevnou šířkou bez kontroly přetečení,
ačkoli dělení 0 je zachyceno a označeno jako chyba. Operátory
a jejich priorita, asociativita a hodnoty jsou stejné jako v
jazyce C.

Na jaké celé číslo s pevnou šířkou se to vztahuje, záleží ve skutečnosti na jakém typu dat se používá (a specifika, proč tomu tak je, jsou mimo toto), ale limitní hodnota je vyjádřena v /usr/include/limits.h tímto způsobem:

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

A jakmile to budete vědět, můžete tento fakt potvrdit takto:

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

Toto je 64bitové celé číslo a to se překládá přímo do shellu v kontextu aritmetického vyhodnocení:

# 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

Mezi 2 a 2-1 tedy získáte záporná celá čísla, která vám ukazují, jak daleko jste od ULONG_MAX. Když vyhodnocení dosáhne tohoto limitu a přeteče, v jakémkoli pořadí, nedostanete žádné varování a ta část vyhodnocení se resetuje na 0, což může způsobit neobvyklé chování s něčím jako pravá asociativní například umocňování:

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

Pomocí sh -c 'command' nic nemění, takže musím předpokládat, že je to normální a vyhovující výstup. Nyní, když si myslím, že mám základní, ale konkrétní pochopení aritmetického rozsahu a limitu a toho, co to znamená v shellu pro vyhodnocení výrazů, myslel jsem, že bych mohl rychle nakouknout, jaké datové typy používá jiný software v Linuxu. Použil jsem nějaký bash Zdroje Musel jsem doplnit vstup tohoto příkazu:

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

if nabízí více výstupů příkazy a mohu hledat příkaz jako awk taky atd. Všiml jsem si, že regulární výraz, který jsem použil, nezachycuje nic o libovolných přesných nástrojích, které mám, jako je bc a dc .

Otázky

  1. Jaký je důvod, proč vás nevarovat (jako awk dělá při vyhodnocení 2^1024), když vaše aritmetické vyhodnocení přeteče? Proč jsou koncovému uživateli vystavena záporná celá čísla mezi 2 a 2-1, když něco hodnotí?
  2. Někde jsem četl, že některá varianta UNIXu může interaktivně změnit ULONG_MAX? Slyšel jste o tom někdo?
  3. Pokud někdo svévolně změní hodnotu maximálního čísla bez znaménka v limits.h , poté znovu zkompiluje bash , co můžeme očekávat, že se stane?

Související:Jak provádět celočíselné a floatové výpočty v bash nebo jiných jazycích/rámcích?

Přijatá odpověď:

Mezi 2^63 a 2^64-1 tedy získáte záporná celá čísla, která vám ukazují, jak daleko jste od ULONG_MAX.

Ne. Jak jste na to přišel? Ve vašem vlastním příkladu je maximum:

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

Pokud „přetečení“ znamenalo „dostanete záporná celá čísla, která vám ukazují, jak daleko jste od ULONG_MAX“, pak když k tomu přidáme jedno, neměli bychom dostat -1? Ale místo toho:

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

Možná chcete říct, že toto je číslo, které můžete přidat do $max získat záporný rozdíl, protože:

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

To však ve skutečnosti nadále neplatí:

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

Je to proto, že systém používá k implementaci celých čísel se znaménkem dvojkový doplněk. Hodnota vyplývající z přetečení NENÍ pokusem poskytnout vám rozdíl, záporný rozdíl atd. Je to doslova výsledek zkrácení hodnoty na omezený počet bitů a poté její interpretace jako celé číslo se znaménkem dvojky. Například důvod $(($max + 1 + $max)) vyjde jako -1, protože nejvyšší hodnota v doplňku dvou je nastavena všemi bity kromě nejvyšší bit (který označuje záporný); sčítat je dohromady v podstatě znamená přenést všechny bity doleva, takže skončíte s (pokud by velikost byla 16 bitů, a ne 64):

11111111 11111110

Vysoký (znaménkový) bit je nyní nastaven, protože byl přenesen do sčítání. Pokud k tomu přidáte ještě jeden (00000000 00000001), máte všechny bity nastaveny , což ve dvojkovém doplňku je -1.

Myslím, že to částečně odpovídá na druhou polovinu vaší první otázky — „Proč jsou záporná celá čísla… vystavena koncovému uživateli?“. Za prvé, protože to je správná hodnota podle pravidel 64bitových čísel dvojkového doplňku. Toto je běžná praxe většiny (ostatních) obecných programovacích jazyků na vysoké úrovni (nenapadá mě žádný, který by to neuměl), takže bash dodržuje konvenci. Což je také odpověď na první část první otázky — „Jaké je zdůvodnění?“:toto je norma ve specifikaci programovacích jazyků.

WRT 2. otázka, neslyšel jsem o systémech, které interaktivně mění ULONG_MAX.

Pokud někdo svévolně změní hodnotu maxima bez znaménka v limitech.h, pak znovu zkompiluje bash, co můžeme očekávat, že se stane?

Nezáleželo by na tom, jak aritmetika vyjde, protože to není libovolná hodnota, která se používá ke konfiguraci systému – je to hodnota pro pohodlí, která ukládá neměnnou konstantu odrážející hardware. Analogicky můžete předefinovat c být 55 mph, ale rychlost světla bude stále 186 000 mil za sekundu. c není číslo používané ke konfiguraci vesmíru – je to dedukce o povaze vesmíru.

Související:Python – Žádný takový soubor nebo adresář, ale vidím to!?

ULONG_MAX je úplně stejný. Je odvozen/vypočítán na základě povahy N-bitových čísel. Změna v limits.h byl by velmi špatný nápad, kdyby se tato konstanta někde použila za předpokladu, že má reprezentovat realitu systému .

A realitu vnucenou vaším hardwarem nemůžete změnit.


Linux
  1. Co pro vás může udělat shell dotfile

  2. Přizpůsobení prostředí Bash

  3. Účel klíčového slova „dělat“ v Bash For Loops?

  1. Rozšíření cesty Bash/shell pro Mkdir, Touch atd?

  2. Chápete význam `$_`?

  3. Proč dokument Parent Shell Here nefunguje pro dílčí příkaz v Dash, ale funguje Bash?

  1. Proč Regex v Bash funguje pouze v případě, že je proměnnou a ne přímo?

  2. Režim IDE / Emacs pro skriptování Shell v Bash/Sh atd

  3. Proč potřebujete umístit #!/bin/bash na začátek souboru skriptu?