普通、変数はバイト単位で扱います。ビット演算を行っても、結局変数はバイト単位で使わなければいけませんでした。今回は、本当にビット単位で変数を扱うことができる方法を話しましょう。
では、今回の要点です。
では、いってみましょう。
今回は西暦2000年問題の謎を解いてみましょう。
西暦2000年問題。それは1990年代末に世界を騒がせた問題です。それは、西暦を下2桁で扱っているプログラムが西暦2000年を西暦1900年と間違え、誤作動を起こしてしまう可能性がある、という問題でした。
年を扱うプログラムというのは多岐にわたり、様々なところでプログラムの修正、もしくは使用ソフトの変更などが行われてきました(多分)。その甲斐あってか、実際に問題の起きたケースは非常に少なく、平穏に時が流れていきました(多分)。(気象庁などで全く別の原因の閏年問題が起きましたが。)
西暦を下2桁で扱っていた理由は「データのサイズが減らせるから」でした。何百万、何千万といったデータを扱えば、1つ1つのデータ量を1バイト減らしただけでも1メガバイト、10メガバイトとデータが減らせるわけです。今でこそハードディスクは数十ギガバイトが当たり前ですが、昔は記憶媒体のサイズも小さく、また、メモリのサイズも小さく、こういった努力は非常に重要でした(多分)。
しかし、ここで疑問が残ります。年月日を扱うときに、西暦を下2桁で扱うと本当にデータ量が減らせるのでしょうか? 今回はそれを追求してみます。
先ず、年月日というデータはどのような量なのでしょうか?
年は、これといって範囲は決まっていません。しかし、コンピュータで扱う以上範囲は設定しなければいけません。とりあえず、西暦を生の値で保存したければ2バイトは必要となります。
次は月です。月は1〜12まであり、1バイトの変数に入れられそうです。
次は日です。日は1〜31まであり、これも1バイトの変数に入れられそうです。
ということで、年月日をまともに扱おうとすると4バイトの大きさになることが分かります。
ここで、年を下2桁で扱おうとするとどうなるでしょうか? 下2桁となると範囲は0〜99となり、1バイトにおさまります。ということで、合計3バイトとなり、節約になりました。
しかし、これでは無理に下2桁にしなくてもいいとは思いませんか? 0〜255まで使えるのですから、せめて西暦2099年くらいまでは使えるようにしてくれていてもいいじゃないですか。
となると、まだデータ量を小さくする秘策がありそうです。もう少し、年月日というデータを突き詰めてみましょう。
先ず、月です。月は1〜12まであります。よく考えてみると、このデータは0〜15、つまり4ビットで表現できることが分かります。
次に、日です。日は1〜31まであります。これもよく考えてみると、このデータは0〜31、つまり5ビットで表現できることが分かります。
3バイトよりデータ量を少なくしようとすると、上の2つを足して1バイトと1ビットなのですから、最低でも2バイトは必要でしょう。ということで、残り7ビットに年を入れればいいわけです。
7ビットで表現できるデータは0〜127です。ということは0〜99なら何とか入ります。ですが、それ以上は中途半端なのでこのあたりでうち切ろうと思います。
ということで、西暦を下2桁で扱えばデータは2バイトにおさまってしまうわけです。
この2バイトで収まってしまうところがあの騒ぎの原因になってしまった(多分)ことを考えると、いっそのこと収まらなかった方がよかったのではと思いたくなりますね。
さて、ビット演算でこれらのことを行おうとしてもかなり面倒です。C/C++言語にはこれらのことを自動的にしてくれる便利な機能があります。それがビットフィールドです。
使い方は簡単です。構造体の宣言をするとき、メンバ変数名の後ろに : <ビット数> を書けばいいだけです。この変数のサイズはそこで指定されたビット数になります。
まぁ、とりあえず見てみましょう。
struct BDate { unsigned nYear : 7; unsigned nMonth : 4; unsigned nDay : 5; };
このようにすれば、nYear の大きさは7ビット、nMonth の大きさは4ビット、nDay の大きさは5ビットになります。
ここで、型を見て下さい。ビット単位で変数を割り当てるのですが、その時型はどうなるのでしょうか? サイズは自前で決めているのですから、あまり型には意味がないことが分かります。分かっていればいいのは符号付きか符号無しかだけですね。
ということで、ビットフィールドに使う変数の型は unsigned int, signed int, int に限定されています。ツールによっては制限をなくしているかもしれませんが、これが基本です。
unsigned int は unsigned と、signed int は signed と略せるので(これはビットフィールドに限ったことではありません)、unsigned か signed を使うと分かりやくてよいでしょう。int と signed int, signed は同じです。signed を int としても別に構わないでしょう。
これで、西暦2000年問題は出ますが、年月日を2バイトで扱うことができました。
しかし、ビットフィールド全体のサイズは強制的に int と同じサイズ、もしくはその倍数にされてしまいます。昔は int のサイズが2バイトでしたのでこれでよかったのですが、今では4バイトになってしまってかえってサイズが増えてしまいますね。
そして、この年には実際は0〜127のデータが入ります。プログラムによっては西暦2028年問題が起きる可能性も...あるかもしれませんね。
そして、Windowsは西暦1980年からの経過年を使っています。ここでもそれプラス0〜99年までということをやっています。つまり現在のWindowsは西暦2080年問題を抱えているということです。これがWindowsだけの話かどうかは知りませんが、まだまだ安心はできないかもしれませんね...と脅しておきましょう(ニヤリ)。
聞いた話によると、平成10年にプログラムが誤作動するというプログラムもあったそうで、これはもう論外ですね。
最後は、ビットフィールドの色々な特徴について触れます。本当は2章に分ければいいのでしょうが、ビットフィールドに2章も割くつもりはないので。
先ず、ビットフィールドは構造体のメンバの一部であっても使えます。例えば、
struct BDate { unsigned nYear : 12; unsigned nMonth : 4; unsigned nDay : 5; int nWeekday; };
ということもできます。この場合、nYear から nDay までが int のサイズ、もしくはその倍数となります。つまり、32ビット機であれば、この構造体の全体のサイズは8になります。
さらに、ビットフィールドはクラスと共用体でも使えます。
そして、ビットフィールドのアドレスは取得できません。アドレスはバイト単位の通し番号なので、当たり前ですね。
また、ビットを飛ばすこともできます。
struct BDate { unsigned nYear : 12; unsigned nMonth : 4; unsigned nDay : 5; unsigned : 8; unsigned nWeekday : 3; };
名前を付けなければ、その部分は飛ばされます。名前なしの変数が作られていると思っても構いません。
また、int のサイズ以上のサイズを割り当てることはできません。
struct BDate { unsigned nYear : 33; // エラー unsigned nMonth : 4; unsigned nDay : 5; };
そして、int のサイズの境界をまたぐことはできません。
struct BDate { unsigned nYear : 30; unsigned nMonth : 4; unsigned nDay : 30; };
nYear のサイズは30ビットです。int のサイズが32とすると、あと2ビットしか残っていません。すると、nMonth はここに収まらないので、次の32ビットの領域に割り当てられます。
そして、次の nDay は 残り28ビットの領域に入らないので、さらに次の32ビット領域に割り当てられます。
このせいで、一見8バイトに収まりそうなこの構造体のサイズは12バイトになってしまいます。
最後に、強制的に次の int のサイズの境界に移すこともできます。
struct BDate { unsigned nYear : 12; unsigned : 0; unsigned nMonth : 4; unsigned : 0; unsigned nDay : 5; };
名前のない、サイズ0のビットフィールドを作ろうとする感じですね。こうすると、ここで強制的に次の int のサイズの境界に移ることになります。
一見4バイトに収まりそうなこの構造体のサイズは、何と12バイトになります。
駆け足でしたが、これらのことは何となく頭に入れておく程度でいいでしょう。ビットフィールドを実際に使おうとしたとき、ここを見返したのでいいでしょうね。
それでは、今回の要点です。最後に色々書きましたが、特に重要なのはこれくらいでしょうか。
次回は構造体を扱う上で注意しなければならない、とても重要なことをお話しします。同時に面白いマクロも紹介します。それでは。
Last update was done on 2000.8.7
この講座の著作権はロベールが保有しています