閉じる

アライメントとパディングとは?構造体のメモリをやさしく解説

構造体のサイズが、メンバの合計より大きくなるのはなぜでしょうか。

答えは、CPUがデータへ効率よくアクセスするための配置ルールにあります。

本記事ではアライメントとパディングをやさしく説明し、構造体のサイズや並びの見え方がスッキリ理解できるように解説します

初心者向けに、最小限のルールとコツに絞って進めます。

構造体のメモリ基礎とバイト境界

用語の整理

まずは用語を短く揃えておくと全体像がつかみやすくなります

ここでは直感的に理解できる説明に絞ります。

バイトはメモリの最小単位で、アドレスで位置が表せます。

1バイトずつアドレスが増え、0x1000の次は0x1001というように並びます

オフセットは、基準位置からの距離です。

構造体の先頭から何バイト目かを示す数字がオフセットです

アライメントは、型ごとに決まったバイト境界へデータを揃えるルールです。

例えば4バイト境界に揃えるとは、アドレスが4の倍数の位置に置くことです

パディングは、アライメントのために挿入される隙間の埋め草です。

プログラムからは使われないけれど、サイズには含まれる余白のバイトです

バイト境界の基本

「Nバイト境界」とは、アドレスがNの倍数である位置を指します

例えば0x1000は8バイト境界にも4バイト境界にもありますが、0x1004は4バイト境界で、8バイト境界ではありません。

CPUは特定の型を特定の境界へ置くと速く安全に読めるように設計されています。

次の表は、境界の言い方のイメージです。

倍数で考えると迷いにくくなります

境界サイズ意味の要約例のアドレス
1バイト境界どこでもよい0x1001, 0x1002
2バイト境界2の倍数に置く0x1000, 0x1002
4バイト境界4の倍数に置く0x1000, 0x1004
8バイト境界8の倍数に置く0x1000, 0x1008

構造体の並びとオフセットのイメージ

構造体の各メンバは、それぞれのアライメントが満たされるように並べられます

その結果、メンバの間に余白が入ることがあります。

例として次の構造体を考えます。

  • struct Example { char c; int i; short s; }

一般的な環境ではcharは1バイト境界、intは4バイト境界、shortは2バイト境界に置かれます。

配置のイメージとオフセットは次のようになります

オフセット内容バイト数備考
0c1charは1バイト境界でOK
1〜3パディング3次のintを4の倍数に揃えるため
4〜7i4intは4バイト境界
8〜9s2shortは2バイト境界
10〜11末尾パディング2構造体全体を4の倍数に揃えるため

この場合、合計サイズは12バイトになり、メンバの合計1+4+2=7バイトより大きくなります

アライメントとは

メモリアライメントの意味と目的

アライメントとは、CPUが扱いやすい境界にデータを置くことで、アクセスの正確性と性能を確保する仕組みです

これによりCPUは1回の読み書きで値を取得しやすくなり、思わぬ遅延や例外を避けられます。

型ごとの境界サイズの目安

多くの環境での目安として、型のサイズと同じか近い境界に揃えられることが多いです

以下は代表例です。

実際はコンパイラとCPUに依存します。

型の例典型サイズ 32bit典型サイズ 64bit典型アライメント
char111
short222
int444
float444
double888
ポインタ484 または 8

表はあくまで目安です。

実機やコンパイラで必ず確認してください。

不明なときはsizeofとalignofで測るのが一番確実です

アライメントとアクセス効率の関係

データが適切に揃っていると、CPUは少ないメモリアクセスで値を読み出せるため高速です

一方で、ズレた場所にあると追加の読み書きが発生したり、まれにハードウェアで禁止され例外になることもあります。

通常のCコードで自然な並びに任せていれば問題は起きにくいです。

C言語のアライメント

Cでは、各メンバはその型のアライメントに従って配置され、構造体全体のアライメントはメンバの中で最大のものになります

また構造体サイズは、そのアライメントの倍数になるように末尾にパディングされます。

C11以降ではalignof演算子と_Alignofが使えます。

指定したい場合はalignas_Alignasで強制できますが、初心者のうちは「ルールを理解して配置を工夫する」方が安全です。

packedやpragmaでアライメントを崩す指定は、読み書きが遅くなったり未定義動作の原因になるため、必要になるまで避けましょう

まずは自然な配置に任せ、観測して学ぶのが近道です

パディングとは

メモリパディングの仕組み

パディングは、次のメンバを正しい境界に乗せるために自動で挿入される余白バイトです

プログラムから直接使うことはなく、値は意味を持ちません。

読み書きの対象はあくまでメンバそのものです。

パディングに入っているビットの内容は未規定です。

値を当てにしたり、構造体全体をバイト比較して同一性を判定するのは避けましょう。

常に「パディングは余白」と考えるのが安全です

フィールド間パディングと末尾パディング

パディングには、メンバ同士の間に入るものと、構造体の末尾に足されるものの2種類があります

前者は次のメンバの境界合わせ、後者は構造体自体のアライメント合わせのためです。

例えばcharの後にintを置くと、intを4の倍数に揃えるために数バイトの隙間が入ることがあります。

この「隙間」は使えないけれど、sizeofにはカウントされます

sizeofに現れるサイズ差

sizeofで得られる構造体のサイズは、メンバの合計サイズより大きくなることがあります

これはフィールド間パディングと末尾パディングの合計が加わるためです。

例として、charとdoubleをこの順に並べると、doubleを8バイト境界に置く都合で間に7バイトのパディングが挟まるケースがあります。

よくある配置例と注意点

小さな型の後に大きな型を置くとパディングが増えがち、というのが初心者にとって最重要の注意点です

charやshortを先頭に並べるより、doubleやポインタなど大きい型を先に置くと、パディングを減らしやすくなります。

ビットフィールドなどは別ルールが絡むため、最初は避けると理解が進みます。

構造体のメモリ配置のコツ

フィールド順序でパディングを減らす

基本の並べ方は「大きい型から小さい型へ」です

これだけで多くのケースでパディングが減ります。

例えば次の2通りを比べると違いが分かります。

  • 悪い並びの例: char c; double d; int i;
  • 良い並びの例: double d; int i; char c;

良い並びの方が、間に挟まるパディングが少なくなり、sizeofが小さくなることが多いです

同じサイズの型をまとめるコツ

同じサイズのフィールドを連続させると、次のフィールドのために追加のパディングが入りにくくなります

例えば、intとintを続けて置けば、その間にパディングは通常不要です。

charを複数使う場合は、配列char name[16]のようにまとめると管理もしやすくなります。

32bit 64bit環境の違いの目安

32bitと64bitで特に変わりやすいのはポインタとlong doubleなど一部の型です

多くの環境ではポインタが32bitでは4バイト、64bitでは8バイトになります。

そのため、ポインタを含む構造体は64bit環境で大きくなりやすいです。

クロスプラットフォームを意識する場合は、サイズに依存しない設計や実測確認が大切です。

サイズとアライメントの確認方法

最も確実なのは、実際にコンパイルしてsizeofとalignof、offsetofで観測することです

C11ならstdalign.hのalignofが使えます。

C言語
#include <stdio.h>
#include <stddef.h>   // offsetof
#include <stdalign.h> // alignof in C11

struct S {
    char   c;
    int    i;
    double d;
};

int main(void) {
    printf("sizeof(struct S) = %zu\n", sizeof(struct S));
    printf("alignof(struct S) = %zu\n", alignof(struct S));
    printf("offsetof(S, c) = %zu\n", offsetof(struct S, c));
    printf("offsetof(S, i) = %zu\n", offsetof(struct S, i));
    printf("offsetof(S, d) = %zu\n", offsetof(struct S, d));
    return 0;
}

出力を見れば、どこにパディングが入り、構造体全体がどの境界に揃えられているかが一目で分かります

古い環境では_Alignofやコンパイラ拡張が必要なことがあります。

まとめ

アライメントはデータを適切なバイト境界に置くルール、パディングはそのために生まれる余白で、sizeofに現れる差の正体です

多くの構造体では「大きい型から小さい型へ」「同サイズをまとめる」という並び替えだけで、無駄なパディングを減らせます。

さらに、32bitと64bitでポインタなどのサイズが変わる点を意識し、実際にsizeofやalignof、offsetofで確かめる習慣を持てば、見積もり違いに悩まされにくくなります。

まずは手元の環境で観測し、結果とルールを結び付けて覚えることが、理解への最短ルートです

プログラミング - データ表現と数値

この記事を書いた人
エーテリア編集部
エーテリア編集部

このサイトでは、プログラミングをこれから学びたい初心者の方に向けて記事を書いています。 基本的な用語や環境構築の手順から、実際に手を動かして学べるサンプルコードまで、わかりやすく整理することを心がけています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!