雑記
数値 - 危険物取り扱い注意
最終更新:
匿名ユーザー
-
view
コンピュータの中では、数値はすべて2進数で表されるのでした。
そして、負数は最上位bitがOnとなった、2の補数表記で表されるのでした。
では、例えば
signed char c; signed int i; c = -1; /* 0xff */ i = c;
のようなコードを書いた場合、どうなるのでしょう。
32bit環境ならば、当然intは32bitです。残りの24bitはどうやって埋めるのでしょう?
この場合、「符号拡張」が起こります。符号付(signed)な値の場合、より大きな型に代入する際には上位24bit分には代入する数値の符号で埋められます。上の例だと、-1は0xffとなり、符合は1です。したがって、上位24bitが1で埋められ、0xffffffffとなるわけです。
これは32bitの場合での2の補数表記となっており、辻褄があっています。
問題は、この符号拡張がプログラマが意識していないタイミングで行われることがあるということです。
例えば、
#define HOGE (0xff) signed char c = 0xff; ・・・ switch(c) { case HOGE: printf("HOGE!\n"); break; default: printf("PIYO!\n"); break; }
などとした場合、正しく評価されません。おそらく、「HOGE」と表示されることは無いでしょう。
また、同様に
char c; signed char* cp; c = -1; cp = &c; if (*cp == 0xff) { ・・・ }
などという使い方も危険です。
なぜならば、(処理系にもよりますがANSIでは)switchステートメントで評価する際にはintとして扱うので上記の符号拡張が行われてしまうのです。
また、*演算子を使用して取り出したポインタの指し先が保持している値もintで評価されてしまうことがあるからです。
また、*演算子を使用して取り出したポインタの指し先が保持している値もintで評価されてしまうことがあるからです。
つまり、switch(0xffffffff) if(0xffffffff == 0xff)で評価されてしまうため、0xffと一致しなくなってしまうのです。
これを避けるためには、このような使い方をする変数は明示的にunsignedとして宣言するなどをすることです。
上の例ではあえてsignedで宣言していますが、signed/unsignedをつけないで宣言した変数(単にchar cなどとした変数)が符号付か符合なしかは処理系依存です。運がよければ正常動作しますが、移植性は損なわれてしまいます。
上の例ではあえてsignedで宣言していますが、signed/unsignedをつけないで宣言した変数(単にchar cなどとした変数)が符号付か符合なしかは処理系依存です。運がよければ正常動作しますが、移植性は損なわれてしまいます。
C言語はいくつかのサイズの違う型を扱うことができます。
その際に、このような変換をコンパイラがすることがあるのです。
その際に、このような変換をコンパイラがすることがあるのです。
これを、「暗黙の型変換」といいます。
おおむね、いちいちキャストしたりしなくていいので便利なのですが。
おおむね、いちいちキャストしたりしなくていいので便利なのですが。
上の例のような場合には注意が必要です。
また、符号の有り無しで同じサイズの変数でも値域が異なることにも注意しなくてはいけません。
例えばcharの場合、signed char は-128~127、unsigned charなら0~255の値域を持つわけで。
signed char sc = 100; unsigned char uc = 100;
としたとき、sc * 2 は -56、uc * 2 は 200(どちらも0xc8)となってしまいます。同じ1byteでも符号に注意しないと、結果に思わぬ値が入ってしまうわけです。
ソースコードだけを見ていると、ここで述べたようなことで何らかのバグを出したとしてもなかなか気づくことができません。
ですが、プログラムで扱うデータというのは、内部的にはすべからく数値として扱われています。
したがって、ここで述べたようなことに留意するかしないかで、その人の作るプログラムの質が大きく変わってくると思います。
ですが、プログラムで扱うデータというのは、内部的にはすべからく数値として扱われています。
したがって、ここで述べたようなことに留意するかしないかで、その人の作るプログラムの質が大きく変わってくると思います。