構造体のサイズ合計が異なる原因と対策方法【C言語】

技術

動作環境

・Kubuntu 22.04

構造体のサイズ合計が異なる

C言語で構造体を使用する際に、sizeofで合計のサイズを測るとサイズが構造体の各要素の合計のサイズではない時があります。

以下はサンプルでソースコードを作成しました。

<ソースコード>

#include<stdio.h>

typedef struct
{
    long  test1;
    short test2;
    char  test3;
    long  test4;
    short test5;

}Test;

int main()
{
	  int i = 0;
	  Test a;
	           
	  printf("a_size=%d \n", sizeof(a));
	  printf("test1_size=%d \n", sizeof(a.test1));
	  printf("test2_size=%d \n", sizeof(a.test2));
	  printf("test3_size=%d \n", sizeof(a.test3));
	  printf("test4_size=%d \n", sizeof(a.test4));
	  printf("test5_size=%d \n", sizeof(a.test5));

	  printf("\n");

  	printf("a_adress=%p \n", &a);
	  printf("test1_adress=%p \n", &a.test1);
  	printf("test2_adress=%p \n", &a.test2);
	  printf("test3_adress=%p \n", &a.test3);
	  printf("test4_adress=%p \n", &a.test4);
	  printf("test5_adress=%p \n", &a.test5);

}

上記を実行してみると下記になりました。

今回実行した環境が64bit環境なので、long型が8byteになっています。

実行した結果を確認します。ソースコード上の構想体のデータサイズを数えると、21byteですが、実際の実行結果は32byteになっていました。

サイズが異なっています。

これはどうゆうことなのでしょうか。

データ型のアライメントが起こっている

理由はデータ型のアライメントとパディングが発生している結果ということになります。

データ型のアライメントとはプログラムの変数を使用する際に、変数のサイズ分のメモリを確保する際に保存アドレスの位置を調整することです。

もう少しざっくり言うと、PC(機械側)がデータの読み書きがしやすいように、アドレスの配置を調整するということです。

また、上記の実行結果のように、test3(char)型のアドレスが0xXX9a(0xXXの部分は割愛部分)ですが、次のtest4(long)型のアドレスは0xXXa0となっており、5byte分メモリ的には余る箇所があります。

このあまりの箇所には隙間を埋めるために無意味なデータが追加されています。この処理をパディングと言います。

このアライメントとパディングの処理の結果で本来のサイズより構造体のサイズが大きくなってしまったということになります。

何が問題か

通常は特に問題は出てこないと思います。

しかし、データサイズを細かく管理したり、メモリ管理が厳しいプログラムやデータサイズが決まっているデータ間の処理などではアライメントやパディングを意識しないと、実装時に整合性が合わなくなったり、データのやり取りができなかったりするため注意が必要です。

対策方法

「#pragma pack(n)」というものを使用します。

構造体のサイズを計算したサイズと同じサイスにしたい場合は「#pragma pack(1)」を記述すれば、1byte間隔でアライメントされるようになります。ソースコードの一部分に適応したい場合は、適応したい終わりの箇所で「#pragma pack(pop)」を記述します。

先ほどのサンプルソースに下記を追加しました。

#include<stdio.h>

#pragma pack(1)
typedef struct
{
    long  test1;
    short test2;
    char  test3;
    long  test4;
    short test5;
}Test;
#pragma pack(pop)

int main()
{
	  int i = 0;
	  Test a;
	           
	  printf("a_size=%d \n", sizeof(a));
	  printf("test1_size=%d \n", sizeof(a.test1));
	  printf("test2_size=%d \n", sizeof(a.test2));
	  printf("test3_size=%d \n", sizeof(a.test3));
	  printf("test4_size=%d \n", sizeof(a.test4));
	  printf("test5_size=%d \n", sizeof(a.test5));

	  printf("\n");

  	printf("a_adress=%p \n", &a);
	  printf("test1_adress=%p \n", &a.test1);
  	printf("test2_adress=%p \n", &a.test2);
	  printf("test3_adress=%p \n", &a.test3);
	  printf("test4_adress=%p \n", &a.test4);
	  printf("test5_adress=%p \n", &a.test5);
}

上記を実行すると下記になります。

本来のサイズと実行結果のサイズが合うようになりました。

参考記事

【C++】パディングとアライメント
こんにちは。 制作部プログラマーの日髙です。 以前にブログを投稿したのはインターンの時になるので一年
【初見殺し】アラインメント - Qiita
本題解説の前に、簡単なクイズを出します。メモリを意識する言語やDBを扱ったことがある方なら、なんとなくで答えられるはずです。実行環境はVisual Studio2019を使用し、64bitでビル…
パディングとは - IT用語辞典
パディングとは、詰め物、水増し、埋草などの意味を持つ英単語。ITの分野では、表示要素の内側に設けられた余白や、データを一定の長さに整形するため前後に挿入される無意味なデータなどをこのように呼ぶ。プログラミングやネットワーク通信などで、データ...

おわりに

アライメントとパディングは通常は意識しないと思いますが、問題が発生した時に気づかないと解決に時間がかかる要素です。

自分もアライメント関係の問題が発生した時に全然気づかず、かなりの時間を費やして、理由がわかった時は、「あぁぁ、、、そうゆうことか、、、くそ~(疲労感)」と思ったことがあります。

ですので、知識として持っておくと良いかもしれません。私も思い出せるようにしたいと思います。