ビットフィールドと共用体

ビットフィールドと共用体
2005 年 11 月 18 日
1 構造体のワードアライメント
struct Data {
char name[ 3 ] ;
int point ;
/* 32bit コンピュータで 4byte */
} ;
struct Data array[ 4 ] ;
printf( "%d\n" , sizeof( array ) ) ; /* array の配列の大きさは? */
1. このようなプログラムを実行し、array の配列の大きさを求めると、いくつになるであろ
うか?
2. name が 3byte,point が 4byte よって 4 * 7 = 28[byte] と思うかもしれないが、ほとんど
の 32bit コンピュータでは 32[byte] という結果を返すはずである。なぜだろうか?
最近のコンピュータは、 CPU の処理速度に比べ、メモリのアクセス速度は遅い。このため CPU
とメモリの間でデータをやりとりする際には、一括してデータの読み書きを行う。 32bit コン
ピュータであれば、 4byte を 1 単位 (ワード) として、最低でも 4[byte] 単位でデータを読み書
きする。このように一括してデータを読み込む時の単位の区切り (境界) を、ワードアライメン
ト (ワード境界) と呼ぶ。
+0
+1
+2
+3
+0
+1
+2
+3
0
0
1
2
3
0
0
1
2
3
4
4
5
6
7
4
4
5
6
7
8
9
10
11
9
10
11
12
13
14
15
←8,9,10,11を一括
して読込む
4*N
8
4*N
12
13
14
15
name
point
struct Data が詰まって配置されたら
7,8,9
= array[1].name
10,11,12,13 = array[1].point
array[1].point を読む時は、
8,9,10,11の行と12,13,14,15の行の
2度のメモリアクセスが発生する。
ワード境界をまたがったデータの読み込みは、 CPU にとって 2 度のメモリアクセスが必要で
あり、処理速度が遅くなる。
このため、構造体をメモリに配置する場合は、データがワード境界をまたがないように配置す
ることが多い。この例であれば、 3[byte] の name の後ろに 1[byte] の空き領域を作り、struct Data
を 8[byte] で扱う方が処理速度の向上につながる。 (ただしメモリはすこし無駄使いとなる)
Visual C++ なら構造体宣言の前の行に 『#pragma pack』と書けば、ワード境界を無視し
て『メモリ領域を節約して配置』してくれる。
1
2 1つの変数に複数の値を保存
例えば、西暦の年月日を記憶する場合、簡単な宣言であれば、
struct YMD {
int year ;
int month ;
int mday ;
} ;
といった構造体で保存ができる。しかし、このデータを大量記憶する必要があった場合、1デー
タあたり (int=4byte なら) 12byte の記憶領域が必要である。
しかし、西暦なら 12bit もあれば、 0 ∼ 4096 年を記憶でき、月を 4bit , 日を 5bit で記憶す
れば、合計 21bit で年月日を記憶できる。例えば、この年月日のデータを、 2 進数表現で
Y,YYYY,YYYY,YYYM,MMMD,DDDD
(YY...Y = 年 , MMMM = 月 , DDDDD = 日)
のように記憶することもできる。1
int ymd( int year , int month , int mday ) {
return (year << 9) | (month << 5) | mday ;
} ;
int ymd_year( int ymd ) { return (ymd >> 9) ; }
int ymd_month( int ymd ) { return (ymd >> 5) & 0xF ; }
int ymd_mday( int ymd ) { return ymd & 0x1F ; }
void main() {
int today = ymd( 2005 , 11 , 18 ) ;
printf( "%d/%d/%d\n" , ymd_year( today ) ,
ymd_month( today ) ,
ymd_mday( today ) ) ; /* "2005/11/18" */
/* ymd の 11 月を 2 月に変更 */
today = (today & 0x1FFE1F) | (2 << 5) ;
/* 0x1FFE1F = ((1<<12)-1) | ((1<<5)-1) */
}
printf( "%d/%d/%d\n" , ymd_year( today ) ,
ymd_month( today ) ,
ymd_mday( today ) ) ; /* "2005/2/18" */
(例)
2005 年 11 月 18 日を 2 進表現
011111010101,0000,00000
(2005 << 9)
000000000000,1011,00000
| (11 << 5)
(or) 000000000000,0000,10010
| 18
----------------------------011111010101,1011,10010
しかし、このプログラムのような 2 進数演算を書くのは、面倒であり間違いやすい。通常は
注ぎに示すビットフィールドを使用する。
1
10 進数で 年 *10000 + 月 *100 + 日という方法もある。 (2005 年 11 月 18 日 = 20051118) しかし、 2 進数
の方が、シフト演算と bit 論理演算で簡単にできる。
2
3 構造体のビットフィールド
C 言語では、構造体の内部のメモリ使用量を減らすために、宣言に一工夫を施せば、コンパ
イラが必要に応じた2進数演算を行ってくれる。この機能をビットフィールドと呼ぶ。
struct YMD {
unsigned int year : 12 ;
unsigned int month : 4 ;
unsigned int mday : 5 ;
} ;
void main() {
struct YMD today ;
today.year = 2005 ; today.month = 11 , today.mday = 18 ;
printf( "%d/%d/%d\n" , today.year , today.month , today.mday ) ;
}
/* ymd の 11 月を 2 月に変更 */
today.month = 2 ;
printf( "%d/%d/%d\n" , today.year , today.month , today.mday ) ;
ビットフィールドでは、要素が数値データの場合、要素の宣言の後ろに『:』と、そのデータ
を保存するための bit 数を書けば良い。2
ただし、ビットフィールドの要素のアドレスを取り出すことはできない。
struct YMD today ; /* 以下のような処理はできない */
scanf( "%d %d %d" , &today.year , &today.month , &today.mday ) ;
4 共用体
1つのデータの中に、文字列か実数のデータをいずれかを保存したい場合には、共用体が便利
である。共用体では構造体と同じような宣言だが、その要素を記憶するための場所は、同じ場所
が使われる。
union Data {
char name[ 8 ] ;
double value ;
} ;
union Data x ;
strcpy( x.name , "tsaitoh" ) ;
x.value = 1.234 ; /* tsaitoh の文字データの上に 1.234 が上書きされる */
printf( "%s" , x.name ) ; /* tsaitoh とは表示されず変な値が表示される */
2
前の2進数演算での例では、年月日のデータを下位ビットから並べているが、ビットフィールドでは、データを
下位ビットから並べるか上位ビットから並べるかは、規定されていない。
3
しかし共用体だけでは、そのデータの中に何が入っているのか解らないため、共用体が単独で
使われることは少ない。1つの配列の中に異なるいずれかのデータが入っているプログラムを作
るなら、以下のようになるであろう。
struct Data {
int
type ;
/* 要素 x の中身を区別するため */
union {
char str[ 8 ] ; /* x の中身は 文字列か */
double value ;
/*
実数の場合 */
}
x ;
} ;
void setString( struct Data* p , char* s ) {
p->type = 0 ;
strcpy( p->x.str , s ) ;
}
void setValue( struct Data* p , double d ) {
p->type = 1 ;
p->x.value = d ;
}
void print( struct Data* p ) {
switch( p->type ) {
case 0 : printf( "%s\n" , p->x.str ) ; break ;
case 1 : printf( "%f\n" , p->x.value ) ; break ;
}
}
void main() {
int i ;
struct Data array[ 3 ] ;
setString( &array[ 0 ] , "abc" ) ;
setValue( &array[ 1 ] , 1.234 ) ;
setString( &array[ 2 ] , "def" ) ;
}
for( i = 0 ; i < 3 ; i++ )
print( &array[ i ] ) ;
/* 配列の中身を表示 */
/* abc,1.234,def が表示されるはず */
4