GNU/Linux >> Belajar Linux >  >> Linux

Alasan Bash Shell Tidak Memperingatkan Anda Tentang Aritmatika Overflow Dll?

Ada batasan yang ditetapkan untuk kemampuan evaluasi aritmatika bash kerang. Manual ini ringkas tentang aspek aritmatika shell ini tetapi menyatakan:

Evaluasi dilakukan dalam bilangan bulat dengan lebar tetap tanpa pemeriksaan overflow,
meskipun pembagian dengan 0 terjebak dan ditandai sebagai kesalahan. Operator
dan prioritas, asosiatif, dan nilainya sama seperti di
bahasa C.

Integer lebar tetap mana yang dimaksud yang benar-benar tentang tipe data which digunakan (dan secara spesifik mengapa ini melebihi ini) tetapi nilai batas dinyatakan dalam /usr/include/limits.h dengan cara ini:

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

Dan setelah Anda mengetahuinya, Anda dapat mengkonfirmasi keadaan fakta ini seperti ini:

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

Ini adalah bilangan bulat 64 bit dan ini diterjemahkan langsung di shell dalam konteks evaluasi aritmatika:

# 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

Jadi antara 2 dan 2-1, Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari ULONG_MAX. Ketika evaluasi mencapai batas itu dan meluap, dengan urutan apa pun, Anda tidak mendapatkan peringatan dan bagian dari evaluasi itu disetel ulang ke 0 yang dapat menghasilkan beberapa perilaku yang tidak biasa dengan sesuatu seperti asosiasi-kanan eksponensial misalnya:

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

Menggunakan sh -c 'command' tidak mengubah apa pun jadi saya harus menganggap ini adalah output yang normal dan sesuai. Sekarang saya pikir saya memiliki pemahaman dasar tetapi konkret tentang rentang dan batas aritmatika dan apa artinya di shell untuk evaluasi ekspresi, saya pikir saya dapat dengan cepat mengintip tipe data apa yang digunakan perangkat lunak lain di Linux. Saya menggunakan beberapa bash sumber Saya harus melengkapi input dari perintah ini:

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

Ada lebih banyak output dengan if pernyataan dan saya dapat mencari perintah seperti awk juga dll. Saya perhatikan ekspresi reguler yang saya gunakan tidak menangkap apa pun tentang alat presisi arbitrer yang saya miliki seperti bc dan dc .

Pertanyaan

  1. Apa alasan untuk tidak memperingatkan Anda (seperti awk lakukan ketika mengevaluasi 2^1024) ketika evaluasi aritmatika Anda meluap? Mengapa bilangan bulat negatif antara 2 dan 2-1 diperlihatkan kepada pengguna akhir saat dia mengevaluasi sesuatu?
  2. Saya pernah membaca bahwa beberapa rasa UNIX dapat mengubah ULONG_MAX secara interaktif? Ada yang pernah dengar ini?
  3. Jika seseorang secara sewenang-wenang mengubah nilai unsigned integer maximum di limits.h , lalu kompilasi ulang bash , apa yang bisa kita harapkan akan terjadi?

Terkait:Bagaimana cara melakukan perhitungan integer &float, dalam bash atau bahasa/kerangka kerja lain?

Jawaban yang Diterima:

Jadi antara 2^63 dan 2^64-1, Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari ULONG_MAX.

Tidak. Bagaimana menurut Anda? Dengan contoh Anda sendiri, maksimumnya adalah:

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

Jika "meluap" berarti "Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari ULONG_MAX", lalu jika kita menambahkan satu, bukankah kita akan mendapatkan -1? Tapi sebaliknya:

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

Mungkin maksud Anda ini adalah nomor yang dapat Anda tambahkan ke $max untuk mendapatkan selisih negatif, karena:

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

Tapi ini sebenarnya tidak terus berlaku:

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

Ini karena sistem menggunakan komplemen dua untuk mengimplementasikan bilangan bulat bertanda. Nilai yang dihasilkan dari overflow BUKAN merupakan upaya untuk memberi Anda perbedaan, perbedaan negatif, dll. Ini secara harfiah merupakan hasil dari pemotongan nilai ke bit dalam jumlah terbatas, kemudian ditafsirkan sebagai bilangan bulat bertanda komplemen dua. Misalnya, alasan $(($max + 1 + $max)) keluar sebagai -1 karena nilai tertinggi dalam komplemen dua adalah semua bit yang ditetapkan kecuali bit tertinggi (yang menunjukkan negatif); menambahkan ini bersama-sama pada dasarnya berarti membawa semua bit ke kiri sehingga Anda mendapatkan (jika ukurannya 16-bit, dan bukan 64):

11111111 11111110

Bit (tanda) tinggi sekarang disetel karena terbawa dalam penambahan. Jika Anda menambahkan satu lagi (00000000 000000001) ke dalamnya, maka Anda telah semua bit disetel , yang dalam komplemen duanya adalah -1.

Saya pikir itu sebagian menjawab bagian kedua dari pertanyaan pertama Anda — “Mengapa bilangan bulat negatif… diekspos ke pengguna akhir?”. Pertama, karena itu adalah nilai yang benar menurut aturan bilangan komplemen dua 64-bit. Ini adalah praktik konvensional dari sebagian besar (lainnya) bahasa pemrograman tingkat tinggi tujuan umum (saya tidak bisa memikirkan yang tidak melakukan ini), jadi bash adalah mengikuti konvensi. Yang juga merupakan jawaban untuk bagian pertama dari pertanyaan pertama — “Apa alasannya?”:ini adalah norma dalam spesifikasi bahasa pemrograman.

WRT pertanyaan ke-2, saya belum pernah mendengar tentang sistem yang secara interaktif mengubah ULONG_MAX.

Jika seseorang secara sewenang-wenang mengubah nilai unsigned integer maximum di limit.h, lalu mengkompilasi ulang bash, apa yang bisa kita harapkan akan terjadi?

Tidak ada bedanya dengan cara aritmatika keluar, karena ini bukan nilai sembarang yang digunakan untuk mengonfigurasi sistem — ini adalah nilai kenyamanan yang menyimpan konstanta abadi yang mencerminkan perangkat keras. Dengan analogi, Anda dapat mendefinisikan ulang c menjadi 55 mph, tetapi kecepatan cahaya masih akan 186.000 mil per detik. c bukan angka yang digunakan untuk mengonfigurasi alam semesta — ini adalah deduksi tentang sifat alam semesta.

Terkait:Python – Tidak ada file atau direktori seperti itu tetapi saya dapat melihatnya!?

ULONG_MAX persis sama. Itu disimpulkan/dihitung berdasarkan sifat bilangan N-bit. Mengubahnya di limits.h akan menjadi ide yang sangat buruk jika konstanta itu digunakan di suatu tempat dengan asumsi itu seharusnya mewakili realitas sistem .

Dan Anda tidak dapat mengubah kenyataan yang dipaksakan oleh perangkat keras Anda.


Linux
  1. Apa yang dapat dilakukan shell dotfile untuk Anda

  2. Menyesuaikan shell Bash

  3. Bash Echo Baris Perintah Dieksekusi Di Baris Perintah Itu Sendiri (bukan Dalam Script)?

  1. Perluasan Pathname Bash/shell Untuk Mkdir, Touch Dll?

  2. Memahami Arti `$_`?

  3. Mengapa Dokumen Induk Shell Di Sini Tidak Berfungsi Untuk Sub-perintah Di Dash Tapi Bash Bekerja?

  1. Mengapa Regex Di Bash Hanya Bekerja Jika Itu Adalah Variabel Dan Tidak Secara Langsung??

  2. Menjaga Anda di Loop – Contoh Bash Untuk, Sementara, Sampai Loop

  3. Mode IDE / Emacs untuk skrip Shell di Bash/Sh, dll