今回は共用体です。
共用体の定義は以下のように行います。
union タグ名 { メンバ1; メンバ2; ... }; |
一見すると構造体っぽい定義方法で、同じように見えるかもしれませんが、構造体と、共用体ではメモリ確保のされ方が異なります。
このことを確認するために、サンプルを1つ実行してみます。
#include <stdio.h> struct t_CHARACTER { char c0; char c1; char c2; char c3; }; union u_CHARACTER { char c0; char c1; char c2; char c3; }; int main() { struct t_CHARACTER data1; union u_CHARACTER data2; data1.c0 = 'a'; data1.c1 = 'b'; data1.c2 = 'c'; data1.c3 = 'd'; data2.c0 = 'e'; data2.c1 = 'f'; data2.c2 = 'g'; data2.c3 = 'h'; printf( "\ndata1 --\n" ); printf( "%c\n", data1.c0 ); printf( "%c\n", data1.c1 ); printf( "%c\n", data1.c2 ); printf( "%c\n", data1.c3 ); printf( "\ndata2 --\n" ); printf( "%c\n", data2.c0 ); printf( "%c\n", data2.c1 ); printf( "%c\n", data2.c2 ); printf( "%c\n", data2.c3 ); printf( "\n" ); printf( "\ndata1 address --\n" ); printf( "%p\n", &data1.c0 ); printf( "%p\n", &data1.c1 ); printf( "%p\n", &data1.c2 ); printf( "%p\n", &data1.c3 ); printf( "\ndata2 address --\n" ); printf( "%p\n", &data2.c0 ); printf( "%p\n", &data2.c1 ); printf( "%p\n", &data2.c2 ); printf( "%p\n", &data2.c3 ); return 0; } |
実行結果
data1 -- a b c d data2 -- h h h h data1 address -- 0x28cc8c 0x28cc8d 0x28cc8e 0x28cc8f data2 address -- 0x28cc8b 0x28cc8b 0x28cc8b 0x28cc8b |
構造体の場合は、各メンバ変数が異なるメモリ領域に割り当てられますが、共用体の場合は同じアドレスが使用されます(場所が”共用”されます)。上記サンプルのdata2ではc0~c3へ順に値をセットしていますが、これは同一番地への書き込みとなり、最終的にc3の値が有効になります。
上記の例だと、共用体のメリットがわからないかと思いますので、もう少し役に立ちそうな別の例を2つほど出してみます。
#include <stdio.h> union u_test { int idata; unsigned char cdata[4]; }; int main() { union u_test data2; data2.idata = 10; printf( "\ndata2(idata) --\n" ); printf( "%p %d\n", &data2.idata, data2.idata ); printf( "\ndata2(cdata) --\n" ); printf( "%p %04x\n", &data2.cdata[0], data2.cdata[0] ); printf( "%p %04x\n", &data2.cdata[1], data2.cdata[1] ); printf( "%p %04x\n", &data2.cdata[2], data2.cdata[2] ); printf( "%p %04x\n", &data2.cdata[3], data2.cdata[3] ); return 0; } |
実行結果
data2(cdata) -- 0x28cc8c 10 data2(cdata) -- 0x28cc8c 000a 0x28cc8d 0000 0x28cc8e 0000 0x28cc8f 0000 |
このプログラムでは、4byteのint変数に整数値をセットしたとき、その値が内部的にどのように格納されているかを確認することができます。今回はintelのCPU上でコンパイルしたのですが、若い番地のアドレスに下位ビットの情報が入っていることがわかりました。このようなメモリ確保の方法を、小さい番地から使っているのでリトルエンディアン(little endian)と呼びます。
プログラムを実行しているCPUによっては、以下の出力になる場合もあります。
これは、データを大きい番地から使っているのでビッグエンディアン(big endian)と呼びます。
data2(cdata) -- 0x28cc8c 0000 0x28cc8d 0000 0x28cc8e 0000 0x28cc8f 000a |
2番目の例です
この例は、セットされたデータを複数の方法でアクセスする事ができるというサンプルです。このような書き方をすることは多くないですが、共用体の各メンバが指す先頭アドレスが同一だという事がわかればOKです。
#include <stdio.h> #include <string.h> struct DETAIL { char c0; char c1; char c2; char c3; }; union DATA { char str_array[4]; struct DETAIL d; }; int main() { union DATA c; strcpy( c.str_array, "abc" ); printf( "\ndata2(idata) --\n" ); printf( "%p %s\n", &c.str_array, c.str_array ); printf( "\nc(cdata) --\n" ); printf( "%p %c\n", &c.d.c0, c.d.c0 ); printf( "%p %c\n", &c.d.c1, c.d.c1 ); printf( "%p %c\n", &c.d.c2, c.d.c2 ); printf( "%p %c\n", &c.d.c3, c.d.c3 ); return 0; } |
実行結果
data2(idata) -- 0x28cc8c abc c(cdata) -- 0x28cc8c a 0x28cc8d b 0x28cc8e c 0x28cc8f |
次の例は、unionが実際にlinux等のOSで使用されている例です(説明しやすいように一部改変してあります)
ちょっと難しいので、よく意味が分からない方は読み飛ばしていただいても結構です。
以下のファイルは/usr/include/ext2fs/ext2fs.hから抜粋したものです。
/* * Structure of an inode on the disk */ struct ext2_inode { unsigned short int i_mode; /* File mode */ unsigned short int i_uid; /* Low 16 bits of Owner Uid */ unsigned int i_size; /* Size in bytes */ unsigned int i_atime; /* Access time */ unsigned int i_ctime; /* Inode change time */ unsigned int i_mtime; /* Modification time */ unsigned int i_dtime; /* Deletion Time */ unsigned short int i_gid; /* Low 16 bits of Group Id */ unsigned short int i_links_count; /* Links count */ unsigned int i_blocks; /* Blocks count */ unsigned int i_flags; /* File flags */ union { struct { unsigned int l_i_version; /* was l_i_reserved1 */ } linux1; struct { unsigned int h_i_translator; } hurd1; } osd1; /* OS dependent 1 */ unsigned int i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ unsigned int i_generation; /* File version (for NFS) */ unsigned int i_file_acl; /* File ACL */ unsigned int i_dir_acl; /* Directory ACL */ unsigned int i_faddr; /* Fragment address */ union { struct { unsigned short int l_i_blocks_hi; unsigned short int l_i_file_acl_high; unsigned short int l_i_uid_high; /* these 2 fields */ unsigned short int l_i_gid_high; /* were reserved2[0] */ unsigned int l_i_reserved2; } linux2; struct { unsigned char h_i_frag; /* Fragment number */ unsigned char h_i_fsize; /* Fragment size */ unsigned short int h_i_mode_high; unsigned short int h_i_uid_high; unsigned short int h_i_gid_high; unsigned int h_i_author; } hurd2; } osd2; /* OS dependent 2 */ }; |
大きなくくりでみると、structの中に2つunionがあります。
これはHDDのファイルをOSが内部管理するための構造体です。
この構造体はlinuxやhurdという複数のOSが利用しており、両OSでデータ構造は、ほぼ同じなのですが一部OSによって依存(dependent)している部分があります。このOS依存の部分をunionとして定義することで、同一の構造体を利用しつつOS依存でフォーマットが異なる部分を吸収しています。
関連記事
コメントを残す