雑記
100 × 200 =32?:変数のオーバーフローの話
最終更新:
匿名ユーザー
-
view
変数には「型」があります。
この「型」って言うのは何なのかというと、
主に値を格納できるサイズの違いです。
主に値を格納できるサイズの違いです。
C言語の場合、整数型の変数の型としては
char、int、short int、long int、long long int
がありますね。
(実数型も入れたらdouble、floatなどもありますが)
(実数型も入れたらdouble、floatなどもありますが)
それぞれ、1byte、2byte、4byte、8byteの領域を持っています。
1byteは8bitでした。なので、1byteで256通りの状態を表せるのでした。
ということは、
2byteなら256×256通り、
4byteなら256の4乗通り、
8byteなら256の8乗通りの状態が表せるわけです。
2byteなら256×256通り、
4byteなら256の4乗通り、
8byteなら256の8乗通りの状態が表せるわけです。
これは逆に言うと、ある型で表せる範囲が限定されているということでもあります。
unsigned long long intなら0~18,446,744,073,709,551,615まで表せるわけで、まあ普通の数値であれば収まるとは思いますが。
でも例えばcharの変数には1000なんて数字を表すことはできません。
これは結構、不便なことなのです。それは、
例えばunsigned charの変数ca,cbがあったとします。
この二つの変数の乗算の結果を受け取るためには、
unsigned shortの変数が必要となります。
unsigned short同士の乗算であれば、unsigned longがやはり同様に必要です。
この二つの変数の乗算の結果を受け取るためには、
unsigned shortの変数が必要となります。
unsigned short同士の乗算であれば、unsigned longがやはり同様に必要です。
なぜかというのはお分かりだと思います。
計算の結果が変数の表せる値の範囲におさまらないことがあるためです。
計算の結果が変数の表せる値の範囲におさまらないことがあるためです。
このような「変数の表せる値の範囲におさまらない」値を代入しようとした時には、はみ出した部分は捨てられてしまいます。これを「変数のオーバーフロー」といいます。
例えば、
unsigned char ca = 100, cb = 200; ca *= cb;
などとするとcaには20000(0x4E20)が入って欲しいわけですが、caは1byteしかないためはみ出した部分(0x4E)が捨てられます。
なので、結果としてcaには32(0x20)が格納されるわけです。
なので、結果としてcaには32(0x20)が格納されるわけです。
100×200の答えが、32になってしまうわけです。恐ろしいですね。
同様に、100 + 200も300(0x012C)ではなく44(0x2C)となります。
結局、たとえlong long intを使ったとしても、表せる状態は多くなりますが限界があることには変わりません。
変数の計算(特に加算・乗算)には特に注意をしましょう。
逆に、この切り捨てられることを利用したのがチェックサムと呼ばれるデータの整合性の保障方法です。
あるデータが100byteあるとして、
あるデータが100byteあるとして、
unsigned char dat[100]; unsigned char sum; int i; ・・・ for(i = 0; i < sizeof(dat); i++) { sum += dat[i]; }
などとして1byteのサム(SUMとは合計のこと)をとります。
オーバーフローすることを逆手に取るわけです。
オーバーフローすることを逆手に取るわけです。
通信する際にデータの最後にこのサム値を付加してやり
受信側でも受信後同様の処理を行って、
計算したサムと受信したサム値が一致していればノイズなどがのることなく正常に受信できたと解釈できるわけです。
受信側でも受信後同様の処理を行って、
計算したサムと受信したサム値が一致していればノイズなどがのることなく正常に受信できたと解釈できるわけです。
もちろん、1/255の確立で偶然一致することもありえます。
(例えばデータ{100,200}のチェックサムは0x2cですが、
データ{256,200}のチェックサムも0x2cとなってしまいます)
(例えばデータ{100,200}のチェックサムは0x2cですが、
データ{256,200}のチェックサムも0x2cとなってしまいます)
しかしながら通信におけるデータの整合性チェックは簡便かつ軽量であることも求められるので、クリティカルなデータで無い場合にはこれで十分チェックの役割を果たします。
変数の表せる範囲に限界があるということは、結構見落としがちなことで、思わぬ不具合につながることがあります。
プログラムを書いた本人は思い込みもあるので、ソースを追っていっても不具合の原因がわからず、デバッガでステップ実行していったりして始めて原因がわかるようなことにもなりかねません。
「数値」は最も基本的で、もっとも扱うことの多いデータです。だからこそ、たかが足し算、掛け算であってもプログラミングする際にはキチンと気をつけることが重要だと思います。
【おまけ】
bitを表示するデバッグ用関数。
いろいろな変数を食わせて雰囲気つかんでください。
bitを表示するデバッグ用関数。
いろいろな変数を食わせて雰囲気つかんでください。
使い方:
第一引数にデバッグ用の文字列
第二引数にbitデータのポインタ
第三引数にbitデータのサイズ(byte単位)
※以下のコードはWebできれいに見せるために
全角スペースを含んでいます!!
第一引数にデバッグ用の文字列
第二引数にbitデータのポインタ
第三引数にbitデータのサイズ(byte単位)
※以下のコードはWebできれいに見せるために
全角スペースを含んでいます!!
void vDbgBit(char* str, unsigned char* ucp, size_t len) { unsigned char ucWork, i; printf("[DBG]vDbgBit:%s [", str); for(i = 0; i < (len * 8); i++) { ucWork = 0x80 >> (i % 8); printf("%c", ((ucp[i / 8] & ucWork) ? '1' : '0')); if(!(i / 4)) { printf(" "); } } printf("]\n"); }