C のビット フィールド
このチュートリアルでは、C 言語のビット フィールドについて学習します。
ビットフィールドから議論を始めます。 次に、ビットフィールドの格納について説明し、続いて C 言語でのビットフィールドの構文について説明します。
最後に、さまざまなデータ型のビット フィールドを見て、内部にビット フィールドを持つ構造体が消費する最小および最大のスペースを理解します。
C のビット フィールド
プログラミングにおけるビット フィールドは、プログラマがメモリを節約するのに役立つ独自のデータ構造です。 ビット フィールドを使用すると、メモリを構造体にビット単位で割り当てることができます。
黒と白の 2 色だけを持つ純粋な黒と白の画像を考えてみましょう。 0
または 1
の 2つの値のみを格納する必要があります。
100x100 の小さな画像で、各次元に数千のピクセルがある画面を比較してみましょう。 画像には1万ピクセルがあります。
標準データ型では、C 言語の unsigned char
データ型のオプションがあり、これは 1 バイトしか必要としません。 ただし、純粋な白黒画像を保存するには、10000 バイトのメモリが必要です。
ビット フィールドを使用すると、1 バイトに 8 ピクセル (1 バイトに 8 ビット) を保存できます。 これは、10000
ではなく、およそ 1250
バイトが必要になることを意味します。
これは単なる例です。 これは、画像のみの場合にスペースを節約できるという意味ではありません。 何千もの受験者が現れるいくつかの試験では、合格/不合格の情報を保存するのに 1 ビットしか必要ありません。 それ以外の場合は、候補ごとに 1 バイトを使用するオプションがあります。
C のビット フィールド ストレージ
ビット フィールド ストレージに関する議論を開始するには、次の構造を検討してください。
struct {
unsigned char is_married;
unsigned char is_graduated;
} status0;
この構造体には 2 バイトのメモリ空間が必要です。 ただし、両方のフィールドに 0 または 1 を格納する必要があります。 スペースを節約するためのより良い方法に進みましょう。
構文からコードまでのビット フィールドを詳細に見ていきます。
C 言語では、各変数に必要なビット数を示す特定の構文があります。
struct {
type[variable_name] : size; // Size will be in bits
}
この構文は構造体で使用できることに注意してください。 ここで、type
は int、char、short、unsigned char などの任意のデータ型です。
C 言語の有効な変数名については、すでによくご存じでしょう。 コロンは構文の一部であり、変数名とサイズの間に必要です。最後に、size
は必要なビット数です。
次に驚くかもしれないのは、ビット サイズの構造体のサイズです。 次のコードを参照してください。
#include <stdio.h>
struct {
unsigned char is_married;
unsigned char is_graduated;
} status0;
struct {
unsigned char is_married : 1;
unsigned char is_graduated : 1;
} status1;
int main() {
printf("Memory size occupied by status1 : %ld bytes\n", sizeof(status0));
printf("Memory size occupied by status1 : %ld bytes\n", sizeof(status1));
return 0;
}
出力は次のとおりです。
Memory size occupied by status1 : 2 bytes
Memory size occupied by status1 : 1 bytes
出力では、最初の構造体は 2 バイト (つまり、各フィールドに 1 バイト) を使用します。これは非常に論理的です。 ただし、2 番目の出力には 1 バイトが必要です。 2 バイトまたはビットを期待するかもしれません。
問題は、なぜ 1 バイトなのかということです。 ロジックは、データ型が C 言語のルーチン定義ごとに 1 バイトを消費することです。
ただし、サイズをビット単位で指定すると、プログラマはそれぞれ 1 ビットのフィールドをさらに 7つ宣言できます。
合計ビット数は、1 バイトに対して 8 ビット以下でなければなりません。 そうしないと、2 バイトのストレージが消費されます。
次のコードを参照してください。
#include <stdio.h>
struct {
unsigned char a : 1;
unsigned char b : 7;
} status0;
struct {
unsigned char a : 1;
unsigned char b : 1;
unsigned char c : 1;
unsigned char d : 1;
unsigned char e : 1;
unsigned char f : 1;
unsigned char g : 1;
unsigned char h : 1;
} status1;
int main() {
printf("Memory size occupied by status1 : %ld\n", sizeof(status0));
printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
return 0;
}
出力は次のとおりです。
Memory size occupied by status1 : 1
Memory size occupied by status1 : 1
ビット数を増やした次の例を見てみましょう。
#include <stdio.h>
struct {
unsigned char a : 3;
unsigned char b : 7;
unsigned char c : 7;
} status0;
struct {
unsigned char a : 1;
unsigned char b : 1;
unsigned char c : 1;
unsigned char d : 1;
unsigned char e : 1;
unsigned char f : 1;
unsigned char g : 1;
unsigned char h : 1;
unsigned char i : 1;
unsigned char j : 1;
} status1;
int main() {
printf("Memory size occupied by status1 : %ld\n", sizeof(status0));
printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
return 0;
}
出力は次のとおりです。
Memory size occupied by status1 : 3
Memory size occupied by status1 : 2
最初の構造では、合計ビット数は 3+7+7=17 で、16 ビット (2 バイト) よりも大きくなります。 したがって、3 バイトが消費されます。
出力の最初の行にも同じことが表示されます。
2 番目の構造体には、それぞれ 1 ビットの 10 個のフィールドがあり、10 ビットが必要です。 したがって、2 バイトを消費します。 ここでも、出力の 2 行目で概念が確認されます。
C の short
データ型のビット フィールド
ビット フィールドを使用するには、unsigned char
が必要であることがわかるかもしれません。 ビットフィールドは他のデータ型でも使用できます。
ここでは、デモンストレーションを行い、ビット フィールドの概念をより理解するために、short
データ型を検討しています。
次のコードを検討してください。
#include <stdio.h>
struct {
unsigned short a : 3;
} status0;
struct {
unsigned short a : 3;
unsigned short b : 9;
unsigned short c : 4;
} status1;
int main() {
printf("Memory size occupied by status1 : %ld\n", sizeof(status0));
printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
return 0;
}
出力は次のとおりです。
Memory size occupied by status1 : 2
Memory size occupied by status1 : 2
どちらの構造体でも、short
は 2 バイトかかるため、消費されるメモリは 2 バイトです。 したがって、必要な最小メモリは 2 バイトです。
ただし、複数のフィールドが合計で 16
ビット (2 番目の構造体に示されているように) を消費する場合があります。サイズは 2 バイトのままです。
ビット数が 16 から増加すると、short
データ型の次の 2 バイトが自動的にカバーされ、結果として 4 が 4つ消費されます。
次の C プログラムを見てみましょう。
#include <stdio.h>
struct {
unsigned short a : 6;
unsigned short b : 6;
unsigned short c : 7;
} status1;
int main() {
printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
return 0;
}
ここで、合計ビットは 6+6+7=19
なので、出力は次のようになります。
Memory size occupied by status1 : 4
char には 1 バイトのストレージがあるため、メモリがバイトごとに増加することに注意してください。 一方、short
の場合、short
は定義により 2 バイトのストレージを持つため、メモリは 2 バイト増加します。
C の複数のデータ型を持つビット フィールド
ここで、構造体に 2つ以上のデータ型がある場合のビット フィールドの格納要件について説明するのが適切な時期です。 このような場合、必要な最小バイトは、構造内の最大のデータ型に必要な最大領域です。
たとえば、構造体に short
と char
のメンバーがある場合、最大の型は short
であり、2 バイトが必要です。 構造体全体で最低 2 バイトが必要です。
int & short
の場合、int が最大の型です。 したがって、構造全体で 4 バイトが必要です。 次のコード例を使用して、概念を示してみましょう。
#include <stdio.h>
struct {
unsigned short a : 6;
unsigned int b : 6;
} status1;
struct {
unsigned long long a : 6;
unsigned int b : 6;
unsigned short c : 6;
} status2;
int main() {
printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
printf("Memory size occupied by status1 : %ld\n", sizeof(status2));
return 0;
}
出力は次のとおりです。
Memory size occupied by status1 : 4 Memory size occupied by status1 : 8
最初の構造体では、int が最大のデータ型です。 したがって、出力の最初の行は 4 バイトの構造サイズを示しています。
2 番目の構造体では、long long
が優勢な型であるため、出力の 2 行目に構造体の 8 バイト サイズが表示されます。
最後に、複数のデータ サイズの場合のインクリメントは単純です。 増分ステップは、サイズが最大タイプの構造体のバイト数に等しいことです。
理解を深めるために、次のコードを調べてみましょう。
#include <stdio.h>
struct {
unsigned short a : 6;
unsigned int b : 30;
unsigned long long c : 50;
} status1;
int main() {
printf("Memory size occupied by status1 : %ld\n", sizeof(status1));
return 0;
}
構造体には 50+30+6=86
ビットがあり、8 バイトは 64 ビットです。 したがって、次の出力に示すように、この構造にはダブル スペースが必要です。
Memory size occupied by status1 : 16
ID 8 + 8 バイトである 16 バイトが必要です。
まとめ
プログラムで複数ビット フィールドを使用している場合は、スペースを節約できます。 宣言時に構造体メンバ変数のビット数を指定できます。
構造体の最小サイズは、構造体の最大のデータ型に必要な最大バイト数です。
1つまたは複数のフィールドが構造体の最大の型のサイズよりも多くのビットをまとめて消費する場合、消費されるメモリは最大メモリ型のサイズの倍数になります。