Qual é a razão para o shell bash não lhe alertar sobre estouro aritmético, etc.?

7

Existem limites definidos para os recursos de avaliação aritmética do shell bash . O manual é sucinto sobre esse aspecto da aritmética de shell, mas os estados :

Evaluation is done in fixed-width integers with no check for overflow, though division by 0 is trapped and flagged as an error. The operators and their precedence, associativity, and values are the same as in the C language.

Qual número inteiro de largura fixa a que isso se refere é realmente sobre o tipo de dados é usado (e os detalhes de por que isso está além disso), mas o valor limite é expresso em /usr/include/limits.h desta maneira:

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

E, quando souber disso, você poderá confirmar esse fato da seguinte forma:

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

Este é um inteiro de 64 bits e isso se traduz diretamente no shell no contexto da avaliação 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

Assim, entre 2 63 e 2 64 -1, você obtém inteiros negativos mostrando o quão longe de ULONG_MAX você está 1 . Quando a avaliação atinge esse limite e transborda, por qualquer ordem que seja, você não recebe nenhum aviso e essa parte da avaliação é redefinida como 0, o que pode gerar algum comportamento incomum com algo como exponenciação associativa à direita para exemplo:

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

O uso de sh -c 'command' não altera nada, portanto, devo assumir que essa é uma saída normal e compatível. Agora que acho que tenho uma compreensão básica, mas concreta, do intervalo e limite aritméticos e o que isso significa no shell para avaliação de expressão, achei que poderia rapidamente ver quais tipos de dados os outros softwares no Linux usam. Eu usei algumas bash sources que tive para complementar a entrada deste 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)

Há mais resultados com as instruções if e posso procurar um comando como awk etc. Observe que a expressão regular que usei não detecta nada sobre ferramentas de precisão arbitrárias que eu tenho como bc e %código%.

Perguntas

  1. Qual é a razão para não lhe avisar (como dc ao avaliar 2 ^ 1024) quando sua avaliação aritmética transborda? Por que os números inteiros negativos entre 2 63 e 2 64 -1 estão expostos ao usuário final quando ele está avaliando algo?
  2. Eu li em algum lugar que algum sabor do UNIX pode alterar interativamente ULONG_MAX? Alguém já ouviu falar disso?
  3. Se alguém alterar arbitrariamente o valor do máximo inteiro sem sinal em awk , recompila limits.h , o que podemos esperar que aconteça?

Nota

1. Eu queria ilustrar mais claramente o que vi, pois é uma coisa empírica muito simples. O que eu notei é que:

  • (a) Qualquer avaliação que forneça < 2 ^ 63-1 está correto
  • (b) Qualquer avaliação que dê = > 2 ^ 63 até 2 ^ 64 dá um negativo número inteiro:
    • O intervalo desse inteiro é x para y. x = -9223372036854775808 e y = 0.

Considerando isto, uma avaliação que é como (b) pode ser expressa como 2 ^ 63-1 mais algo dentro de x..y. Por exemplo, se somos literalmente solicitados a avaliar (2 ^ 63-1) +100 002 (mas pode ser qualquer número menor que em (a)), obtemos -9223372036854675807. Eu estou apenas afirmando o óbvio, eu acho, mas isso também significa que as duas expressões seguintes:

  • (2 ^ 63-1) + 100 002 AND;
  • (2 ^ 63-1) + (LLONG_MAX - {o que a casca nos dá para ((2 ^ 63-1) + 100 002), que é -9223372036854675807}) bem, usando valores positivos que temos;
    • (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
    • = 9223372036854775807 + 100 000

são muito próximos, de fato. A segunda expressão é "2" além de (2 ^ 63-1) + 100 002, ou seja, o que estamos avaliando. Isto é o que quero dizer com você obter inteiros negativos mostrando o quão longe você está de 2 ^ 64. Quero dizer, com esses inteiros negativos e conhecimento dos limites, bem, você não pode terminar a avaliação dentro do intervalo x..y na shell bash, mas você pode em outro lugar - os dados podem ser usados até 2 ^ 64 nesse sentido (eu poderia adicionar em papel ou use em bc). Além disso, no entanto, o comportamento é semelhante ao de 6 ^ 6 ^ 6 como o limite é atingido como descrito abaixo no Q ...

    
por jus cogens prime 27.02.2014 / 16:56

1 resposta

10

So between 2^63 and 2^64-1, you get negative integers showing you how far off from ULONG_MAX you are.

Não. Como você descobre isso? Por seu próprio exemplo, o máximo é:

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

Se "estouro" significa "você recebe números inteiros negativos mostrando o quão longe está de ULONG_MAX você está", então se adicionarmos um a isso, não deveríamos obter -1? Mas em vez disso:

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

Talvez você queira dizer que esse é um número que você pode adicionar a $max para obter uma diferença negativa, já que:

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

Mas isso de fato não continua verdadeiro:

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

Isso ocorre porque o sistema usa o complemento de dois para implementar números inteiros assinados. < sup> 1 O valor resultante de um estouro NÃO é uma tentativa de fornecer a você uma diferença, uma diferença negativa, etc. É literalmente o resultado do truncamento de um valor para um número limitado de bits, em seguida, tê-lo interpretado como um inteiro assinado com um complemento de dois. Por exemplo, a razão $(($max + 1 + $max)) sai como -1 é porque o valor mais alto no complemento de dois é todos os bits definidos exceto o bit mais alto (que indica negativo); somar isso basicamente significa carregar todos os bits para a esquerda para que você fique com (se o tamanho fosse 16 bits e não 64):

11111111 11111110

O bit alto (sinal) agora está definido porque é transportado na adição. Se você adicionar mais um (00000000 00000001) a ele, então terá todos os bits definidos , que no complemento de dois é -1.

Acho que responde parcialmente à segunda metade da sua primeira pergunta - "Por que os inteiros negativos ... são expostos ao usuário final?". Primeiro, porque esse é o valor correto de acordo com as regras dos números de complemento de dois de 64 bits. Essa é a prática convencional da maioria das linguagens de programação de alto nível de propósito geral (não consigo pensar em uma que não faça isso), então bash está aderindo à convenção. Qual é também a resposta à primeira parte da primeira questão - "Qual é a razão?": Esta é a norma na especificação de linguagens de programação.

WRT a segunda questão, eu não ouvi falar de sistemas que interativamente mudam ULONG_MAX.

If someone arbitrarily changes the value of the unsigned integer maximum in limits.h, then recompiles bash, what can we expect will happen?

Não faria qualquer diferença em como a aritmética sai, porque este não é um valor arbitrário que é usado para configurar o sistema - é um valor de conveniência que armazena uma constante imutável refletindo o hardware. Por analogia, você poderia redefinir c como 55 mph, mas a velocidade da luz ainda seja 186.000 milhas por segundo. c não é um número usado para configurar o universo - é uma dedução sobre a natureza do universo.

ULONG_MAX é exatamente o mesmo. É deduzido / calculado com base na natureza dos números de N bits. Alterá-lo em limits.h seria uma péssima ideia se essa constante fosse usada em algum lugar, supondo que ela representasse a realidade do sistema .

E você não pode mudar a realidade imposta pelo seu hardware.

1. Eu não acho que isso (o meio de representação inteira) é realmente garantido por bash , uma vez que depende da biblioteca C subjacente e o padrão C não garante isso. No entanto, isso é o que é usado na maioria dos computadores modernos normais.

    
por 27.02.2014 / 17:53