構造体のメンバとメモリの話

今まで、C言語で構造体のメンバはメモリ上では配列のように連続していると思っていたんですが、どうも必ずしも連続とは限らないことを知ったのでその話。

 

構造体にsizeof演算子を使ってみるとよく分かると思います。 

struct hoge {
short a;
int b;
}

こんな感じでshort型とint型のメンバを1つずつ持ってる構造体があったとして、short→2byte、int→4byteの環境ならsizeof(struct hoge)は6になりそうですが、実行してみるとこうなります。

 

gist2c01e4021e869d8da1cc6b0fa7317ad5

$ gcc struct_size.c
$ ./a.out

sizeof(struct) = 8 byte

 

8byte!!!

ちなみに必ず8byteになるとは限りません。また実行する度に変化(不定,未定義)するのではなく、環境によって変化(処理系定義)します。

 

今度はint bをchar b[2]に変えてみます。

struct hoge {
short a;
char b[2];
}

gistd067f63b315a28ce598bb6745810a705

 

今度はshort→2byte、char [2]→2byteで、普通に4byteになりました。

 

これはアライメントと呼ばれるもので、隙間なくデータを配置するより、あらかじめアクセスしやすい幅(ワード境界を超えないように)に揃えてデータを置いた方が高速にアクセスできることをコンパイラは知っているので、コンパイル時にあえて無駄な領域を作ることがあります。

今回の環境ではアライメントは4byteなので、1つ目の例ではshortの2byteのすぐ後ろにintの4byteを入れてしまうと、intの2byte目でワード境界を超えてしまいます。そのため、shortの後に2byteの無駄な領域(パディングと呼ばれる)を作り、その後ろにintの4byteを置いているため、8byteになります。

ちなみにintとshortの順序を入れ替えると、隙間なく配置してもワード境界を超えないため、 6byteになります。こういうようなアライメントを考えて構造体のメンバの順序などを決めることをアライメント調整と言ったりします。

 

GCCを使っている場合は、拡張機能を使ってアライメントを自分で決めることができます。

struct hoge {
short a;
int b;
} __attribute__((aligned(8)));

こんな感じで変数属性を指定すると、アライメントが8になり、この構造体のサイズは6byteになります。しかし、ハードウェア的なアライメントを変更しているわけではないので、実行速度は少し遅くなります。

他にも、

struct hoge {
short a;
int b;
} __attribute__((packed));

変数属性にpackedを指定すると、必ず隙間なくデータを配置してくれます。

 

ちなみに、このアライメントは、構造体だけでなく配列などでも同様のことが起こります。