[C/C++言語講座] 第1回:ビットの話

はじめに

ゲームプログラミングでは、C/C++の需要が多いです。特にゲーム機では、かなりの会社がC/C++を開発言語としています。Unityを利用したC#での開発も多いようですが、最後の詰めで苦労することがあると聞きます。
弊社ではC/C++でのゲーム開発を行っております。新卒の方を採用した場合、C/C++以前の基礎的な事を学習していない方が多いように思われます。学校では通り一遍の文法的やプログラムの書き方を教え、あとは本人の努力に任せるような教育方法になっているような気がします。直接C/C++に関係なくても、プログラムを書く時の基本的な理解に役立つ、知っておいたほうがいろんな場面で潰しが効く情報を提供しようと思います。

ビット

今回は、ビットの話です。もちろん多くのプログラマはビット、バイトなど基本的なデータ単位については知識があるはずです。
例えば、C言語ではビット単位の記憶域を定義することができます。ビットフィールドですね。新卒の社員に尋ねると知らない人のほうが多いようです。
C言語はもともと記憶領域が小さいコンピュータでOS(UNIX)を動かすように設計されたプログラミング言語ですから、記憶域をできるだけ効率的に利用できるようにという配慮がされています。

記憶装置の最小単位がビットですが、0か1しか表現できないため、ビットを複数集めてより大きな数が表現できるようにします。これがバイト(8ビット)やワード(Windowsでの16ビット)などと呼ばれるデータ型で2進数と呼ばれます。C言語では、char(8ビット)、short(16ビット)、int(32ビット)、long long(64ビット)という整数型が定義されています。これらの整数型はCPUで扱うことのできるデータ単位です。

2進数

ビットで構成されるデータ型、バイトを例にしてどのように数が表現されるか考えてみましょう。

図1

上図は8ビットデータ型を表現したものです。b0~b7は下位から上位への8つのビットを表します。最下位ビットをLSB(Least Significant Bit)、最上位ビットを(Most Significant Bit)と呼びます。各ビットは図上部の2の累乗で表される数値を持ちます。例えばb0が1の場合、20=1となります。同じようにb1が1の場合、21=2となります。それぞれのビットの組み合わせにより0~255までの数字を表すことができるようになります。バイトBの値は次の式のように表されます。

式1

2進数では各桁が0か1の値しかとらないことに注意してください。
b0が1の時に1を加えるとb0の桁は2には成れませんから、b1に繰り上がり、b0は0になります。

このようにb0に1を加えていくと次々と繰り上がりが生じ、最後にはすべてのビットが1になります。この例では、すべてのビットが1になった場合、255という数値を表すことができます。つまり、8ビットのデータは0~255の範囲の数を表すことができます。これは、8ビットの符号なし整数です。

符号付き整数

整数は一般的には±の数値を意味します。C言語でのchar、short、intが符号付であることは既に承知済みでしょう。ビット数に限らず、CPUでは整数は2の補数表現で表されます。補数とは、「足すと次の桁上がりを起こす最小の数字」です。
では、一旦補数は棚に上げて、負の数を8ビット整数で考えてみましょう。
0から1を引くと、8ビット整数は下の図の①のようになります。0-1なので、結果は-1です。つまり、8ビット整数での‐1の表現は、すべてのビットが1であることがわかります。

ちなみに、①は、②のように最上位ビットの左に1を補ったことと同じです。補数の定義を思い出しましょう。「1111 1111」 に「0000 0001」を足すと、②の最上位ビットの左の1が現れ、つまり桁上がりが発生します。この桁上がりが発生する最も小さい数字が「0000 0001」であることは明らかですね。つまり「1111 1111」の2の補数は「0000 0001」ということになります。同様に、「0000 0001」の2の補数は 「1111 1111」となります。このことから、ある数値の2の補数は符号反転した数値であることがわかります。
ここで、2の補数の簡単な求め方を紹介しましょう。ある数値Aの2の補数は、Aのビットを反転させ1を加えることで求められます。「0000 0001」の2の補数は全ビットを反転した「1111 1110」に1を加えた「1111 1111」となり、前述の‐1と一致します。
整数の2進数での表現では、一番左側(最上位)のビットを符号とします。正の数は「0000 0001」から「0111 1111」まで1づつ増えていきます。この値は式1で求めることができ「127」であることがわかります。負の数は「1111 1111」(-1)から順に1を引いてゆき「1000 0000」が最小値(ー128)となります。つまり、8ビットの符号付整数は-128~+127の数字を表すことができるのです。

ビットの重み

ビットの値を2の累乗で毎回計算するのは大変です。つぎのように各ビットの値を覚えておけば、いざというとき(デバッグ時)に役に立ちますよ。
「いち、にぃ、よん、ぱー、いちろく、ざんにい、ろくよん、いちにっぱ、にごろ、ごーいちに、いちまるにぃよん、にぃまるよんぱー、よんまるくんろく、はちいちくんにぃ、いちろくざんぱーすー、さんにいななろくぱー、ろくごーごーさんろく」

16進数

これまでは、ビット表現で8ビット整数を扱ってきましたが、ビット長が長くなるとビット表現のままでは不都合が多くなります。0/1の2値ではなく、10進数と同様に適当な範囲の数値を導入すると考えやすくなります。そこで4ビットを一桁とした16進数が導入されました。4ビットでは「0000」から「1111」の16種類の数(0~15)を表現することができます。16進数のうち0~9は、10進数と同じ数字が使用されますが、10~15はA~Fのアルファベットが割り当てられます。

2進数10進数16進数
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
101010A
101111B
110012C
110113D
111014E
111115F

8ビット整数は2桁の16進数、16ビット整数は4桁の16進数、32ビット整数は8桁の16進数でそれぞれ表すことができます。デバッガでメモリ内容を覗くと、16進数の数値が並んでいます。16進数に慣れていれば、いざというときに役に立ちますよ。

今後16進数を使用し説明を行っていくにあたって、16進数の表記をご紹介しておきます。yyyyは任意の16進数です。

言語表記説明
 Intel系アセンブラ  0yyyyH 先頭の数値がA~Fで始まる場合、頭に0を付ける  
その他アセンブラ$yyyy数値の先頭に’$’を付ける
C/C++0xyyyy数値の先頭に’0x’を付ける


最後にHを付ける表記法は主にアセンブラで使用されますが、初期のドキュメントがアセンブラの知識を前提としていたため、メモリアドレスなどの表記には現在でも使用されます。
フリーで利用できるNASM(Netwide Assembler)では、上記のすべての16進数表記が可能となっています。
また、インテル系アセンブラでは0と1の並びの最後に’b’を付けて2進数を表記することが可能です。(例:01110101b=75H=117)
また、Visual Studio 2015以降であれば、頭に’0b’を付けることで2進数の表記が可能(C++14で導入)となっています。