[前へ]   [目次へ]   [次へ]

変数の内部表現(1)(C/C++)

   (08/10/17) 二進数表記について加筆

今回は、C/C++が変数をどうやってメモリ上に保持しているかです。
まずは、前項での負数になっちゃったパターンの正体です。

まずは、 変数の保持範囲 を思い出してください。
2進法表記では、一桁左にずらすと2倍になります。
1ビットは2個の状態を取るので、2進法と同じ形になります。
つまり、nビットの値が取れる数は 2のn乗通りということになります。
8ビットなら2の8乗、256通りということです。

さて、符号付きの char 型の範囲は -128〜127となっています。
これは 2の7乗、128通り分の負数と、0〜127のおなじく128通り分の正数です。
これが意味することは・・・
「符号付きの場合、1ビットが符号ビットになっている」ということです。

符号ビットは通常最上位ビットに割り当てられています。
最上位ビットが0だと正、1だと負値を意味しています。

なので、二進法表記で0は0000,0000、1は0000,0001、2は0000,0010となっています。

では、-1の時はどのような値が入っているでしょうか。
二進法表記で1000,0001・・・ではありません。
二進法表記では-1は1111,1111と表現されます。
また、-2は1111,1110、-3は1111,1101と減っていき、
-128までいくと1000,0000になります。
これは加減算する時に便利なのです。

   (08/10/17追記)

このページで解説する内容は最も一般的に使用されている表現です。
他にも表現はいくつかあり、1000,0001を-1と扱う表記もあります。
この辺を詳しく知りたい人は「 Wikipedia(外部リンク) 」とか見るといいでしょう。

   (08/10/17追記ここまで)


さて、-1に1を足すと0ですが、
上のような表現をしていると1111,1111に1を足して1,0000,0000になります。

2進数は、2で桁上がりが起きるため、1に1を足すと10になります。
同様に、11に1を足すと2回桁上がりして100になります。99に1を足したのと同じイメージですね。

9ビット目は格納できないので捨てると、0000,0000になり、上手く0になってくれます。
また、-1に-1を足した場合、1111,1111に1111,1111を足して1,1111,1110になります。
また9ビット目を捨てると、1111,1110になり、ちゃんと-2の表現になります。

減算の場合は、9ビット目にあたかも1があるように扱うことで、同様な動作ができます。
-1から1を引くと、1,1111,1111(9ビット目に1を与えています)から0000,0001を引くと、
1,1111,1110になり、9ビット目を捨てると1111,1110になり、-2の表現になります。
また、1から2を引いた場合も1,0000,0001(9ビット目追加)から0000,0010を引くと、
1111,1111になり、ちゃんと-1になりました。

また、一見複雑そうな正負逆転についても、
全てのビットを反転(0を1、1を0に)して1を足すと実現できます。
1を-1にするには、0000,0001を全ビット反転して1111,1110、そこに1を足して1111,1111で-1の完成です。
逆を行う場合も、1111,1111を全ビット反転して0000,0000、そこに1を足せば0000,0001で1になります。
この処理は、符号状態に関係なく行うことができます。

このような表現方法は「2の補数表現」と呼びます。


さて、では保持範囲の限界を超えたらどうなるのでしょうか?
保持範囲の限界がどうであろうと、上の式を適用するだけです。

では、試しに127に2を足してみます。
8ビットの場合、符号付きなら範囲は-128〜127なので、この計算結果は範囲外のはずです。
まず、127の2進数表現は0111,1111で、そこに2(0000,0010)を足します。
結果は1000,0001となりますが・・・、これは-127を示す値です。

本当にそうなるか、やってみましょう。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
#include <stdio.h>

int main(void)
{
   
signed char ch=127;//127で初期化しておく
   
ch+=2;//2を足してみる(ch=ch+2と同様に解釈されます)
   
printf("%d",ch);//表示
   //終了待ち
   
getchar();
   
return 0;
実行結果:
-127
-127が表示されたと思います。
もし、129と表示された場合は、使用しているコンパイラの char 型が8ビットサイズではないということになります。
その場合は、ビット数を説明書などから探して、保持範囲を計算して修正してください。

また、符号なしの場合は、8ビット目は128の桁なので、1000,0001は129を示します。
同様に試してみましょう。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
#include <stdio.h>

int main(void)
{
   
unsigned char ch=127;//127で初期化しておく
   
ch+=2;//2を足してみる(ch=ch+2と同様に解釈されます)
   
printf("%d",ch);//表示
   //終了待ち
   
getchar();
   
return 0;
実行結果:
129
さて、同じ表現値にも関わらず、実際の値が違うということは、何かがそれを識別しています。
お分かりとは思いますが、それは「型」です。
メモリにはビットがならんでいるだけで、それらが何を意味するかというようなことは何処にも書いてありません。
C/C++言語は、変数の型情報を元にどのビットが何を意味するかという意味付けをして処理しています。

ということは、型情報をすり替えたら一体何が起こるのでしょうか?
結論から言えば、値が変化します。

そもそも、型という概念はコンパイラによって提供されているにすぎず、メモリ上には型という概念はないのです。
そのため、コンパイラに別の型として扱わせれば、メモリ上の値は全く別の値と捉えられます。
その一方、メモリ上の情報を複数の型で共有できるように配置すれば、変換すら行わずに(つまり一切の処理を行わずに)
別の型に変化させることが可能だったりします。

しかし、各型とメモリ上の表現にどのような対応関係をつけるかはコンパイラが決めています。
複数の型でメモリ上の情報を共有できるように配置し、またそれを保証する方法は言語仕様にはありません。
すなわち、この技法はコンパイラが変わると正常に実行できない可能性があることを示しています。

また、共有できる配置になっていない状態で上記のようなことをすると、意味不明な値が入っていることになります。
この「型のすり替え」はC/C++の中でも特に機械よりな手法であり、
メモリの実体を意識していなければ正確に扱えないようなシロモノです。

以上のような理由により、この技法は様々な制約の元に成り立ちます。
しかし、ノーコストの型変換の威力は演算量によっては実行速度に目に見える影響を与えるほどであり、
その威力ゆえに、結構多くのアプリケーションがこの技法を用いています。
そして一部のコンパイラもまた、メモリ上の配置をプログラマが任意に決定できる方法を独自に提供しています。

したがって、後々この技法に関しても解説する予定ですが、
威力も高い一方、この技法でバグが発生すると解決が難しくなりやすく、
十分なレベルがないと解決不能なんて事態にもなりかねません。
全ての制約と危険性、注意点などに関して十分な解説を行ってからとなるため、結構先になりそうです。


さてさて、このように符号ビットの存在なんて忘れて計算していてもちゃんと加減算を処理できます。
・・・そうできるように定められているのでしょうけど(笑)

乗算除算はそう簡単にはいきませんが、計算結果のビット数が足りなくなった場合は、下位ビットを残して
上位が捨てられてしまうため、値が本来の値ではなくなってしまいます。

2の補数表現の場合、値の限界を超えるともう一方の値の限界に飛んでいく現象がおきます。
計算結果が保持上限を超えて最低値まで移動することを「オーバーフロー」、
計算結果が保持下限を超えて最大値まで移動することを「アンダーフロー」と呼びます。

C/C++言語でこれらが発生しても、特に何も特殊なことは起こらず、
もう片方の限界値に突っ込んでいきます。
C/C++言語でオーバーフローやアンダーフローが起きたことを探知するのは難しいので、
必要な場合はオーバーフローやアンダーフローが起きないかを事前に計算するか、
サイズ無制限に計算できるライブラリを使う、または作ることがいいでしょう。


これで、文字入力を用いて演算種別を判別するのに必要な内容が揃いました。
次回を読む前に 第28回 のプログラムを改造して、演算種別を文字入力できるようにしてみてください。
次回は、実際に演算種別を文字で入力するように改造してみます。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2008/10/17