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

出張の都合により、今日は授業を入替え、3年の授業。
構造体の理解については、明日の授業で演習を行う。今日は、振替えで演習室も使えないので、 普通の講義。

ビットフィールド

例えば、生年月日を記憶する場合、構造体を使えば以下のように宣言できるが、 int型(32bit=4byte)であれば、12byteを要する。

struct YMD {
    int  year ;
    int  month ;
    int  day ;
} ;

しかし、生年月日の比較などをする場合であれば、 年月日を10進数の桁に合わせて、日付を 20151109 といった数値で表すことも多い。 この場合であれば、int 型 2^31-1 = 2,000,000,000 にも収まる。 プログラムも解りやすくするのであれば、以下の様な補助関数を準備すれば良い。

int ymd10_year( int ymd ) {
    return ymd / 10000 ;
}
int ymd10_month( int ymd ) {
    return (ymd / 100) % 100 ;
}
int ymd10_day( int ymd ) {
    return ymd % 100 ;
}

しかし、このプログラムでは、日の1〜31までの数字のために、0〜99の10進2桁を使う。 月の1〜12のために0〜99の10進2桁を使う。 また、各桁を抜き出すために、除算を使うため処理も手間がかかる。

そこで、年月日を2進数の桁の組合せで保存することを考える。こうすれば、2進数のビットシフト命令で機械語では扱いやすくなる。

// 年(12bit),月(4bit),日(5bit) = Y,YYYY,YYYY,YYYM,MMMD,DDDD
int ymd2( int y , int m , int d ) {
    return (y << 9) | (m << 5) | d ;
}
int ymd2_year ( int ymd ) {
    return ymd >> 9 ;
}
int ymd2_month( int ymd ) {
    return (ymd >> 5) & 0xF ;
}
int ymd2 _day( int ymd ) {
    return ymd & 0x1F ;
}

しかし、この方法でデータを扱うと、月の値を1つ増やすといった処理を書こうと思うと、2進数の扱いに慣れていないと プログラムも間違いやすい。

int ymd = ymd2( 2015 , 11 , 9 ) ;
// ymd の月を12月に変更したい。
ymd = (ymd  & 0x1FFE1F) | (12 << 5) ;

このような処理のために、ビットフィールドを使用する。使い方は、構造体の要素の宣言の後ろに、": bit数"をかけばいい。 こうすれば、構造体の要素の参照の式をかけば、必要に応じて2進数を使った機械語命令をコンパイラが書いてくれる。

struct YMD {
    unsigned int  year  : 12 ;
    unsigned int  month : 4 ;
    unsigned int  day   : 5 ;
} ;

共用体

構造体と同じような文法の一つに共用体がある。 構造体では、異なる型の各要素のメモリの領域を準備するが、共用体では全要素が同じ場所を使う。 このため、どれか1つの値を覚えるだけでいい場合に使う。

union int_str4 {
    int  data ;
    char str[4] ;
} ;
union int_str4  a[4] ;

a[0].data = 1234 ;
strcpy( a[1].str , "ABC" ) ;
a[2].data = 2345 ;
strcpy( a[3].str , "BCD" ) ;
printf( "%d¥n" , sizeof( a ) ) ; // 4byte×4 = 16

この異なる型を同じ場所に覚えるための文法は、最近のオブジェクト指向のプログラム言語では、仮想関数という考え方 が利用できるので、あまり利用することは少なくなっている。

列挙型

プログラムの中で週のような情報を覚える時、日付の処理を考えると、 日=0,月=1,火=2,水=3,木=4,金=5,土=6 といった割り当てをすることも多い。 しかし、水曜の処理だったら...という時に、if ( week == 3 ) という書き方では、分かりにくい。

int wd = 1 ; // 月初めが月曜の場合...
int day ;
for( day = 1 ; day <= 31 ; day++ , wd = (wd + 1) % 7 ) {
    if ( wd == 3 ) {
        水曜日の処理...
    }
}
// マジックナンバーを使わない場合
#define SUN 0
#define MON 1
#define TUE 2
#define WED 3
  : (略)

int wd = MON ; // 月初めが月曜の場合...
int day ;
for( day = 1 ; day <= 31 ; day++ , wd = (wd + 1) % 7 ) {
    if ( wd == WED ) {
        水曜日の処理...
    }
}

上のプログラムの後半のマジックナンバーを使わない例であれば、プログラムの意味も解りやすくなる。 しかし、#define を7つも書き並べるのは面倒だし、対応する数値を1つづつ増やしながら書くのは、 間違って修正するかもしれない。 こういう場合には、列挙型を用いる。

enum Week {
   SUN , MON , TUE , WED , THR , FRI , SAT
} ;
enum Week wd = MON ;
int day ;
for( day = 1 ; day <= 31 ; day++ ) {
    if ( wd == WED ) {
        水曜日の処理...
    }
    // wd 型は int ではないので、週番号を増やすのはちょっと面倒
    wd = (enum Week)( ( (int) wd + 1 ) % 7 ) ;
}
 

2015年12月

    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

アーカイブ

Google

このブログ記事について

このページは、T-Saitohが2015年11月 9日 10:14に書いたブログ記事です。

ひとつ前のブログ記事は「データベース(SQLの演習)」です。

次のブログ記事は「vmwareサーバの認証鍵問題」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。