C の知識:メモリをケチる
ここで紹介する機能は主にメモリの節約を目的としていますが、これらは実際のところ現代のプログラムで使うべきでない、時代遅れの機能だと思われます。
それは端的に言えば「割に合わないから」です。
これらの機能により節約できるメモリは数バイト程度のものですが、携帯端末でもギガバイト単位のメモリが当たり前で、数百メガのメモリを喰うプログラムが犇めく現代に於いてそれは殆ど誤差の様なものでしょう。
そしてこの数バイトの代償に、以下で説明する機能は
- バイト単位のメモリ管理
- 型安全性
といったプログラムの重要な秩序を脅かします。秩序の乱れはほぼ確実にコードの可読性を低下させ、そして読みにくいコードは紛う事なき悪です。
本物のプログラマは時に悪へと身を堕とさなければなりませんが、それはこの程度の見返りの為にする事ではないのです。
ビットフィールド
構造体の_Bool、signed int、unsigned int型メンバには、ビット単位で領域を割り当てる事ができます。例えば
struct | |
{ | |
unsigned int a: 1; | |
unsigned int b: 2; | |
signed int c: 3; | |
} x; |
では、a、b、cの範囲がそれぞれ 0~1、0~3、-4~3(2の補数の場合)となります。
型としてintも指定できますがビットフィールドの場合は特別で、そのメンバが符号付きになるか符号無しになるかは処理系依存です6.7.2#5。
隣接したビットフィールドは空きがある限り同じ領域に入りますが、
- ビットフィールドに対して何バイト単位で領域を与えるか
- 領域の下から詰めるか上から詰めるか
- 空きが十分でない場合にビットフィールドが分割されるか否か
は全て処理系依存です6.7.2.1#10。gcc、clang、msvc は何れも下位ビットから分割はせず、4 バイト単位(バイトオーダーはプロセッサ次第)でビットフィールドを構築している様ですが、それを前提にコーディングすべきではないでしょう。
共用体
書き方としては構造体のstructがunionに変わるだけですが、メモリ上では各メンバが並ぶのではなく、全てが同じ位置から重複する様に配置されます。例えば
union foo | |
{ | |
signed char a; | |
unsigned int b; | |
}; | |
union foo x; |
により共用体xの領域の中には 1 バイト目にsigned charが、1~4 バイト目にunsgned intがそれぞれ配置されます。
メモリ | ||||
---|---|---|---|---|
: | ||||
(xの 1 バイト目) | a | b | ||
(xの 2 バイト目) | ||||
(xの 3 バイト目) | ||||
(xの 4 バイト目) | ||||
: |
重なっている訳なので当然、一方の書き込みによりもう一方のデータは破壊されます。
x.b = 0xffffffff; | |
x.a = 0; | |
/* x.b == 0xffffff00 for little endian */ |
データの具体的な表現は環境に依存しているので(「C の詳細:型の定義」を参照)、「charで書いてintで読む」様なコードがまともに動くと期待すべきではありません。(符号無し整数型同士ですら結果はバイトオーダーに依存します。)