🧳

C言語の構造体パッキング

に公開

はじめに

構造体という言葉に馴染みのある方ない方いらっしゃると思いますが、構造体パッキングという考え方・手法にフォーカスして記事を執筆します。
もし、使用される言語で構造体がある場合は参考程度にしていただけると幸いです。

マシンスペック

MacBook Air M2 arm64

事前知識

構造体とは

複数のデータ型をまとめた型というイメージです。C言語はデータ型としてcharやint, shortなどがありますが、構造体を使用してAというクラスのようなものを定義し、各モジュールがその構造体に基づいて値を設定することができます。名簿の構造体に、名前や部署などを定義して使用するイメージです。
この構造体は便利な機能の一つですが、構造体に配置する型の順番によってはメモリを占有する割合が変わってきます。今回はそれを明らかにしていきます。

準備

Dockerをホスティング、リモート接続

docker run -it --rm -v $(pwd):/mnt gcc:13 bash
apt update && apt install -y vim make

比較用のC言語のプログラムを準備する。

通常構造体(非最適化)のメモリ配置

vim norm_struct.c
#include <stdio.h>
#include <stddef.h>

struct Normal {
    char a;
    int b;
    char c;
    long int d;
};

int main() {
    printf("[Normal struct]\n");
    printf("sizeof(struct Normal): %lu\n", sizeof(struct Normal));
    printf("offset a: %lu\n", offsetof(struct Normal, a));
    printf("offset b: %lu\n", offsetof(struct Normal, b));
    printf("offset c: %lu\n", offsetof(struct Normal, c));
    printf("offset d: %lu\n", offsetof(struct Normal, d));
    return 0;
}

自力で最適化(隙間なく埋める)

vim self_optimize_struct.c
#include <stdio.h>
#include <stddef.h>

struct Optimized {
    long int d;
    int b;
    char a;
    char c;
};

int main() {
    printf("[Optimized struct]\n");
    printf("sizeof(struct Optimized): %lu\n", sizeof(struct Optimized));
    printf("offset d: %lu\n", offsetof(struct Optimized, d));
    printf("offset b: %lu\n", offsetof(struct Optimized, b));
    printf("offset a: %lu\n", offsetof(struct Optimized, a));
    printf("offset c: %lu\n", offsetof(struct Optimized, c));
    return 0;
}

最適化オプションをつける

vim auto_optimize_struct.c
#include <stdio.h>
#include <stddef.h>

struct Normal {
    char a;
    int b;
    char c;
    long int d;
};

int main() {
    printf("[Optimized with -O2]\n");
    printf("sizeof(struct Normal): %lu\n", sizeof(struct Normal));
    printf("offset a: %lu\n", offsetof(struct Normal, a));
    printf("offset b: %lu\n", offsetof(struct Normal, b));
    printf("offset c: %lu\n", offsetof(struct Normal, c));
    printf("offset d: %lu\n", offsetof(struct Normal, d));
    return 0;
}

実験

通常構造体(非最適化)のメモリ配置

gcc -o normal_struct normal_struct.c
./normal_struct
[Normal struct]
sizeof(struct Normal): 24
offset a: 0
offset b: 4
offset c: 8
offset d: 16

自力で最適化(隙間なく埋める)

gcc -o self_optimize_struct self_optimize_struct.c
./self_optimize_struct
[Optimized struct]
sizeof(struct Optimized): 16
offset d: 0
offset b: 8
offset a: 12
offset c: 13

最適化オプションをつけてみる

gcc -O2 -o auto_optimize_struct auto_optimize_struct.c
./auto_optimize_struct
[Optimized with -O2]
sizeof(struct Normal): 24
offset a: 0
offset b: 4
offset c: 8
offset d: 16

何が起きたのか

C言語では、各型が確保する領域は下記の表の様になります。

型名 サイズ(バイト) 説明
char 1 最小アドレス単位
int 4 整数型(32bit)
long int 8 64bit
short 2 16bit整数
float 4 IEEE 754 単精度
double 8 IEEE 754 倍精度

これを考えてメモリの配置を考えると下記になります。
通常はメモリに隙間(パディング)がありますが、自力で最適化した方にはパディングがほとんどなく、メモリに対して優しい構成になっています。

まとめ

構造体を定義する際は、メモリに寄り添って構造体パッキングをする方がメモリに優しいコードになる。

Discussion