こんにちは。 制作部プログラマーの日髙です。
以前にブログを投稿したのはインターンの時になるので一年半ぶりです。
一人で書くので少し緊張しています。
今回は、最近調べることがあったパディングについて簡単にまとめてみました。
※アライメントとパディングは環境によって変わります。
Visualstudio2019 c++ <x86, x64>で確認しました。
構造体のサイズは?
いきなりですが、この構造体のサイズは幾つになるでしょうか?
struct
{
char a; // 1byte
int b; // 4byte
char c; // 1byte
char d; // 1byte
};
メンバ変数のサイズを合計すると・・・
1 + 4 + 1 + 1 = 7?
答え:12 (sizeof()関数の結果)
この構造体には自動的に5byte分のパディングが挿入されています。
1 + (3) + 4 + 1 + 1 + (2) = 12
先程の構造体に書き込むと以下のようになります。
struct
{
char a; // 1byte
char PADDING_1[3]; // 3byte
int b; // 4byte
char c; // 1byte
char d; // 1byte
char PADDING_2[2]; // 2byte
};
パディングの法則
パディングが入れられる理由ですが、それにはアライメントの知識が必要になるので少し説明します。
アライメント
データ型にはアライメントが設定されていて、変数のメモリを確保する時にはアライメントの倍数アドレスになるように調整されています。
これは、コンピュータにとって都合が良いのでそうなっています。
(今回はパディングがメインなので、詳しい説明は省きます。)
また、アライメントはサイズと同じことが多く、以下のようになっています。
・char型のアライメントは 1
・int 型のアライメントは 4
この値はalignof()関数で確認できます。
メモリのイメージ
それではここから先ほどの構造体に5byteのパディングがどうやって入ったのか、図を使って説明していきます。
↓メモリの図 □は何もないメモリです。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
□ | □ | □ | □ | □ | □ | □ | □ | □ | □ | □ | □ |
(1)char aをメモリに配置
最初の変数は特に何もないのでそのまま0番へ配置されます。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
a | □ | □ | □ | □ | □ | □ | □ | □ | □ | □ | □ |
(2)int bをメモリに配置
次にint bですが、そのまま続けて1番には配置されません。
↓これは間違い
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
a | b | b | b | b | □ | □ | □ | □ | □ | □ | □ |
int型のアライメントは4なので、4の倍数アドレスに配置されます。
この時にaとbの間にできた隙間◆がパディングです。
このメモリは使用されません。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
a | ◆ | ◆ | ◆ | b | b | b | b | □ | □ | □ | □ |
(3)char cを配置
chara型はアライメント1なのでそのまま配置されます。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
a | ◆ | ◆ | ◆ | b | b | b | b | c | □ | □ | □ |
(4)char dを配置
(3)と同じくそのまま詰めて配置されます。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
a | ◆ | ◆ | ◆ | b | b | b | b | c | d | □ | □ |
(5) 構造体のサイズをアライメントに合わせる
構造体のサイズはアライメントの倍数になるようにパディングを入れて調整されます。
アライメントはメンバ内の一番大きい物と同じになります。
今回はintが一番大きいので4になります。
この時点で10byte使用されているので、最後に2byteのパディングを追加して12byteに調整します。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B |
a | ◆ | ◆ | ◆ | b | b | b | b | c | d | ◆ | ◆ |
構造体のメモリ確保は以上になります。
アライメントとパディングは環境次第で多少変わりますが、だいたいこんな感じになっているはずです。
おまけ
順番を変えて4byte節約
struct // 6byte, alignment 2
{
int b; // 4byte
char a; // 1byte
char c; // 1byte
char d; // 1byte
};
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
b | b | b | b | a | c | d | ◆ |
intをshortに
struct // 6byte, alignment 2
{
char a; // 1byte
short b; // 2byte
char c; // 1byte
char d; // 1byte
};
0 | 1 | 2 | 3 | 4 | 5 |
a | ◆ | b | b | c | d |
アライメントを調べる
alignof(int);
アライメントの倍数アドレスにあるか調べる
int ImInt;
if((&ImInt) % alignof(ImInt) == 0)
{ // アライメントOK }
else
{ // アライメントおかしい }
変数が構造体の何バイトめにあるか調べる
struct Hoge
{
char a;
int b;
}
offset(Hoge, b);
最後に
パディングやアライメントに注意しないといけないのは、構造体をバイナリデータにする時です。異なるプラットフォームではパディング(アライメント)が変わる可能性があり、データの互換性がなくなるかもしれません。
また、セーブデータ等を構造体で管理していて、変数を追加したいけどサイズを増やしたくない。そういう時にパディングのメモリ内に収まるように変数を入ると、それまでの構成とサイズをキープしたままデータを追加できたりします。
ここまでパディングについて書いてきましたが、実際に意識する場面は多くありません。何かあった時に(そういえばパディングとかあったな~)と思い出せる程度に、頭の片隅に留めて置ければと思います。
【免責事項】
本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。