本題解説の前に、簡単なクイズを出します。
メモリを意識する言語やDBを扱ったことがある方なら、なんとなくで答えられるはずです。
実行環境はVisual Studio2019を使用し、64bitでビルドしています。
問題1
①「char」のサイズは何バイトでしょう?
②「wchar_t」のサイズは何バイトでしょう?
③「int」のサイズは何バイトでしょう?
④「unsigned long long」のサイズは何バイトでしょう?
#include <stdio.h>
#define DISPLAY_VARIABLE_SIZE(variable) printf(#variable " is %zu byte\n", sizeof(variable))
int main()
{
DISPLAY_VARIABLE_SIZE(char);
DISPLAY_VARIABLE_SIZE(wchar_t);
DISPLAY_VARIABLE_SIZE(int);
DISPLAY_VARIABLE_SIZE(unsigned long long);
return 0;
}
回答
① charは1byte
② whar_tは2byte
③ intは4byte
④unsigned long longは8byte
ですね。簡単でしたか?
char is 1 byte
wchar_t is 2 byte
int is 4 byte
unsigned long long is 8 byte
問題2
では第二問です。こちらの構造体のサイズはいくつでしょうか。
#include <stdio.h>
int main()
{
struct _tag
{
char A;
wchar_t B;
int C;
unsigned long long D;
} ST;
printf("%zu \n", sizeof(ST));
return 0;
}
回答2
1+2+4+8で正解は
ST is 16 byte
!?
これが、アラインメントによる影響です。
アラインメント(alignment)
このような挙動は、上級言語を扱う現代人にはあまり影響がありませんが、ちょっとメモリ管理をかじった人だと「余計なことをしてくれるな。オフセットがズレて把握しにくいし、スタックやメモリを余計に食うじゃないか」という文句が飛んできても仕方ありません。
しかし、このアラインメントは実際にメモリアクセス効率を考えたものになります。
厳密な説明はMSDNを参照してください。
要約すると「各要素は2の累乗(1を除く)境界に整列(alignment)されていると、アクセス効率がよい」というものです。
よって、2のべき乗に満たないものにはパディングが行われます。
Cやアセンブラなど、ネイティブなデータの受け渡しを行った際、構造体が崩れる。などの原因になりえます。
一つのソリューションやプロジェクトで完結している場合は、同じ定義同じコンパイラを使うので意識しなくても問題ありませんが、生データをガシガシやり取りするPOSレジの通信などでは、落とし穴になることがあるようです。
#回避策
では、このアラインメントに抗うことはできないのでしょうか。
アクセス効率の理想に取りつかれたアラインメントですが、昨今のマシンパワーを考えれば、そんな爪に火を灯すようなことをしなくても、十分やっていけると思います。
やり方はいろいろありますが、今回は以下を使って解決してみたいと思います。
#include <stdio.h>
int main()
{
#pragma pack(push,1)
struct _tag
{
char A;
wchar_t B;
int C;
unsigned long long D;
} ST;
#pragma pack(pop)
printf("ST is %zu byte\n", sizeof(ST));
return 0;
}
#pragma pack ここで1,2,4,8,16いずれかを設定することで、設定したbyte数でアラインメントが設定されます。設定を解除するためにはpopを使用します。
##結果
ST is 15 byte
1byteでアラインメントされた結果です。
#まとめ
どの言語においても、初見殺しの仕様は最初躓くとイラっとしますよね。
しかしおじさんからすると、ネイティブな言語は歴史的な背景やメモリ効率、CPU効率が考えられていて、嫌いになり切れません。
ノシ