算術オーバーフローなどを警告しないbashシェルの根拠は何ですか?


9

bashシェルの算術評価機能には制限が設定されています。マニュアルはシェル演算のこの側面については簡潔ですが、次のように述べてます

評価はオーバーフローのチェックなしで固定幅整数で行われますが、0による除算はトラップされ、エラーとしてフラグが立てられます。演算子とその優先順位、結合性、および値は、C言語の場合と同じです。

これが参照する固定幅整数は、実際にはどのデータ型が使用されているか(そしてこれがこれを超えている理由の詳細)ですが、制限値は次のように表されます/usr/include/limits.h

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

そして、それを知ったら、次のようにこの事実の状態を確認できます。

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

これは64ビット整数であり、これは算術評価のコンテキストでシェルで直接変換されます。

# 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

したがって、2 63と2 64 -1の間では、ULONG_MAXからの距離が1であることを示す負の整数が得られます。評価がその制限に達してオーバーフローすると、どのような順序でも警告は表示されず、評価のその部分は0にリセットされます。これにより、たとえば右結合指数などの異常な動作が発生する可能性があります。

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

を使用sh -c 'command'しても何も変化しないので、これは正常で準拠した出力であると想定する必要があります。算術の範囲と制限、および式の評価におけるシェルでの意味が基本的かつ具体的に理解できたと思うので、Linuxの他のソフトウェアが使用するデータ型をすぐに確認できると思いました。bashこのコマンドの入力を補完する必要があるいくつかのソースを使用しました。

{ 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ステートメントの出力が増え、コマンドも同様に検索できるようになりawkました。使用した正規表現では、bcand などの任意の精密ツールについて何も検出されませんdc


ご質問

  1. awk算術評価がオーバーフローしたときに警告しない(2 ^ 1024を評価するときのように)理由は何ですか?なぜ2との間に負の整数である63と2 64 -1、彼は何かを評価するのエンドユーザーに公開しますか?
  2. UNIXのいくつかのフレーバーが対話的にULONG_MAXを変更できることをどこかで読んだことがありますか?これを聞いた人はいますか?
  3. で符号なし整数の最大値を誰かが任意に変更してlimits.hから再コンパイルしたbash場合、何が起こると予想できますか?

注意

1.非常に単純な経験的なものであるため、見たものをより明確に説明したかった。私が気づいたのはそれです:

  • (a)<2 ^ 63-1を与える評価はすべて正しい
  • (b)=> 2 ^ 63から2 ^ 64までの評価では、負の整数が得られます。
    • その整数の範囲はx〜yです。x = -9223372036854775808およびy = 0。

これを考慮すると、(b)のような評価は、2 ^ 63-1とx..y内の何かで表すことができます。たとえば、(2 ^ 63-1)+100 002(ただし(a)よりも小さい任意の数になる可能性があります)を評価するように文字どおりに要求された場合、-9223372036854675807が得られます。私は明白だと思いますが、これは次の2つの式が意味することも意味します。

  • (2 ^ 63-1)+ 100 002 AND;
  • (2 ^ 63-1)+(LLONG_MAX-{シェルが((2 ^ 63-1)+ 100 002)に与えるもの、つまり-9223372036854675807})正の値を使用して、
    • (2 ^ 63-1)+(9223372036854775807-9223372036854675807 = 100 000)
    • = 9223372036854775807 + 100 000

確かに非常に近いです。2番目の式は(2 ^ 63-1)+ 100 002以外の "2"です。つまり、評価対象です。これは、あなたが2 ^ 64からどれだけ離れているかを示す負の整数を取得するという意味です。つまり、これらの負の整数と制限についての知識があれば、bashシェルのx..yの範囲内で評価を完了することはできませんが、他の場所ではできます-その意味で、データは2 ^ 64まで使用できます(追加できます)紙に書いたり、bcで使用したりします。それを超えると、動作は6 ^ 6 ^ 6の動作に似ています。以下のQで説明されているように制限に達しているためです...


5
私の推測では、理論的根拠は「シェルは数学に適したツールではない」ということになります。それはそれのために設計されておらず、あなたが示すようにそれを優雅に扱うことを試みません。地獄、ほとんどのシェルはフロートさえ扱いません!
terdon

@terdonこの場合のシェルによる数値の処理方法は、これまで聞いたすべての高水準言語とまったく同じです。整数型は固定サイズで、オーバーフローする可能性があります。
goldilocks 2014

@terdon確かに、6 ^ 6 ^ 6のタイミングQでこれを調査したので、気づきました。また、コンテンツが見つからなかったのは、C、またはC99に関係しているためだと思いました。私は開発者でもIT担当者でもないので、これらの仮定の背景となるすべての知識を理解する必要があります。確かに、任意の精度を必要とする人はデータ型について知っていますが、明らかに私はその人ではありません:)(しかし、私はawkの@ 2 ^ 53 + 1 @ float doubleの動作に気づきました;精度と内部と印刷の違いなどは私を超えています!)。

1
シェルで大きな数を処理したい場合はbc、たとえばを使用します$num=$(echo 6^6^6 | bc)。残念ながら、bc改行を入れているため、num=$(echo $num | sed 's/\\\s//g')後で行う必要があります。パイプで実行すると、実際には機能しますが、sedで扱いにくい改行文字がありますnum=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')。どちらの場合でも、使用できる整数が得られnum2=$(echo "$num * 2" | bc)ます。
goldilocks 2014

1
... ここで誰かがbc、を設定することにより、この改行機能を無効にできると指摘しましたBC_LINE_LENGTH=0
goldilocks 2014

回答:


11

したがって、2 ^ 63と2 ^ 64-1の間では、ULONG_MAXからどれだけ離れているかを示す負の整数が得られます。

いいえ。どうやってそれを理解しますか? あなた自身の例では、最大は:

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

「オーバーフロー」が「あなたがULONG_MAXからどれだけ離れているかを示す負の整数を取得する」ことを意味する場合、それに1を追加すると、-1になりますか?代わりに:

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

おそらく、これは$max、負の差を得るために追加できる数値であることを意味します。

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

しかし、これは実際には当てはまりません:

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

これは、システムが符号付き整数を実装するために2の補数を使用するためです。1 オーバーフローの結果として得られる値は、差や負の差などを提供しようとする試みではありません。 文字通り、値を限られたビット数に切り捨て、2の補数の符号付き整数として解釈させた結果です。たとえば、理由$(($max + 1 + $max))が-1となるのは、2の補数の最大値が最上位ビット(負を示す)を除くすべてのビットセットであるためです。これらを一緒に追加することは、基本的にすべてのビットを左に運ぶことになるため、最終的には(サイズが16ビットで64ではない場合)、次のようになります。

11111111 11111110

高(符号)ビットは、追加で持ち越されるため、設定されました。これにさらに1つ(00000000 00000001)を追加すると、すべてのビットがセットされ、2の補数は-1になります。

最初の質問の後半に部分的に答えたと思います-「なぜ負の整数は...エンドユーザーに公開されるのですか?」まず、64ビットの2の補数の規則によると、これは正しい値です。これは、ほとんどの(その他の)汎用高水準プログラミング言語の従来の慣習であり(これを行わないものは思いつきません)、bash慣例に従います。また、最初の質問の最初の部分である「根拠は何ですか?」に対する回答でもあります。これは、プログラミング言語の仕様における標準です。

WRTの2番目の質問ですが、対話的にULONG_MAXを変更するシステムについては聞いたことがありません。

誰かがlimits.hの符号なし整数の最大値を任意に変更してからbashを再コンパイルすると、何が起こると予想できますか?

これはシステムの構成に使用される任意の値ではないため、算術演算の結果に違いはありません。これは、ハードウェアを反映する不変の定数を格納する便利な値です。類推すると、cを55 mphに再定義することもできますが、光速は毎秒186,000マイルのままです。cは、ユニバースの構成に使用される数値ではありません。これは、ユニバースの性質に関する推論です。

ULONG_MAXはまったく同じです。Nビット数の性質に基づいて推定/計算されます。 この定数をシステムの現実を表すものと想定してどこかに使用すると、変更するのはlimits.h非常に悪い考えです

また、ハードウェアによって課せられる現実を変えることはできません。


1.これ(整数表現の手段)は実際にはによって保証されているとは思いません。これはbash、基礎となるCライブラリに依存しており、標準のCでは保証されていないためです。ただし、これはほとんどの通常の最近のコンピューターで使用されているものです。


とても感謝しています!部屋の中の象と折り合いをつけて考える。はい、最初の部分ではそれはほとんど言葉についてです。Qを更新して、意図したことを示しました。2の補数が私が見たもののいくつかを説明する理由を調査します。あなたの答えがそれを理解する上で非常に貴重です!UNIX Qに関しては、ここで ARG_MAXとAIXを誤解しているはずです。乾杯!

1
実際、2 *の範囲内にいることが確実であれば、2の補数を使用して値を決定$maxできます。私のポイントは、1)それが目的ではない、2)実行するかどうかを理解していることを確認する、3)適用範囲が非常に限られているためにあまり役に立たない、4)脚注のとおり、システムが実際に動作することが保証されていない2の補数を使用します。要するに、それをプログラムコードで悪用しようとすることは、非常に貧弱な習慣と見なされます。「多数」のライブラリ/モジュール(POSIXでのシェルのbc場合)があります。必要に応じてそれらを使用してください。
goldilocks 2014

最近、私は2の補数を利用して、高速キャリーICを備えた4ビットのバイナリ加算器を備えたALUを実装する何かを見ました。補数との比較さえありました(どのようにオフになっているかを確認するため)。あなたの説明は私がここで見たものに名前を付け、それらのビデオで議論されたものと結びつけることができるようにするのに役立ちました。乾杯!
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.