ビットフィールドと共用体 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
© Copyright 2024 Paperzz