構造体のアラインメント

Visual Studio C++での話。
実は構造体のアラインメントもはまったポイントだった。


症状1
WinPcapが拾ってきてくれる生データを構造体で定義して、拾おうとしたんだけどうまく行かない。。。
意味のないデータが拾われる。

症状2
ARPパケットが送信できない。スニファで確認したら、壊れたパケットが送り出されていた。



結果は構造体のアラインメントのせいだったんですが、なかなか悩んだ部分です。


VisualStudioの場合、プロジェクトのプロパティから設定するか、プラグマで指定するとアラインメントを無効に(というか1バイトに)設定できた。

こんな感じで
#pragma pack(push,1)
struct data{
    unsigned long a;
    unsigned char b[6];
};
#pragma pack(pop)


ちなみに、gccの場合は
struct _DATA {
    unsigned long a;
    unsigned char b[6];
}  __attribute__ ((packed));
もしくは
#pragma pack(1)
struct _DATA {
    unsigned long a;
    unsigned char b[6];
};
#pragma pack(0)

だって。
こんなん、気にした事も無かったよ。
(普通は気にするんだろうけど、移植とか必要無い環境しか知らないので)



構造体のアラインメントがどう働くか調べるために以下の実験を行った。



以下gccで通常時と構造体のアラインメントを変更した場合の差を比べるリスト
#include <stdio.h>

struct stra {
        char c;
        short s;
        int i;
        long l;
        float f;
};

struct strb {
        char c;
        short s;
        int i;
        long l;
        float f;
} __attribute__ ((packed));

void prmem (const void *_p, int n) {
        const unsigned char     *p = _p;
        char    i;

        for(i=0; i<n; ++i, ++p) {
                printf(" %02x", *p);
        }

        printf("\n");
}


int main (void) {
        struct stra a = {
                0x12,
                0x1234,
                0x1234,
                0x12345678,
                2.3
        };

        struct strb b = {
                0x12,
                0x1234,
                0x1234,
                0x12345678,
                2.3
        };

        printf ("struct a :     %8x : ", &a); prmem(&a, sizeof(a));
        printf ("struct a char :                %8x : ", &a.c); prmem(&a.c, sizeof(a.c));
        printf ("struct a short :       %8x : ", &a.s); prmem(&a.s, sizeof(a.s));
        printf ("struct a int :         %8x : ", &a.i); prmem(&a.i, sizeof(a.i));
        printf ("struct a long :                %8x : ", &a.l); prmem(&a.l, sizeof(a.l));
        printf ("struct a float :       %8x : ", &a.f); prmem(&a.f, sizeof(a.f));
        printf ("\n");

        printf ("struct b :     %8x : ", &b); prmem(&b, sizeof(b));
        printf ("struct b char :                %8x : ", &b.c); prmem(&b.c, sizeof(b.c));
        printf ("struct b short :       %8x : ", &b.s); prmem(&b.s, sizeof(b.s));
        printf ("struct b int :         %8x : ", &b.i); prmem(&b.i, sizeof(b.i));
        printf ("struct b long :                %8x : ", &b.l); prmem(&b.l, sizeof(b.l));
        printf ("struct b float :       %8x : ", &b.f); prmem(&b.f, sizeof(b.f));

        return 0;
}



色々な環境で実行してみた。


(1)Sun Ultra SPARCプロセッサ搭載機(Solaris9)
$ gcc -o test_alignment test_alignment.c
$ ./test_alignment
struct a :     ffbffbb8 :  12 3c 12 34 00 00 12 34 12 34 56 78 40 13 33 33
struct a char :                ffbffbb8 :  12
struct a short :       ffbffbba :  12 34
struct a int :         ffbffbbc :  00 00 12 34
struct a long :                ffbffbc0 :  12 34 56 78
struct a float :       ffbffbc4 :  40 13 33 33

struct b :     ffbffba8 :  12 12 34 00 00 12 34 12 34 56 78 40 13 33 33
struct b char :                ffbffba8 :  12
struct b short :       ffbffba9 :  12 34
struct b int :         ffbffbab :  00 00 12 34
struct b long :                ffbffbaf :  12 34 56 78
struct b float :       ffbffbb3 :  40 13 33 33


(2)IBM PowerPC G4プロセッサ搭載機(NetBSD)
$ gcc -o test_alignment test_alignment.c
$ ./test_alignment
struct a :      ffffdc18 :  12 00 12 34 00 00 12 34 12 34 56 78 40 13 33 33
struct a char :         ffffdc18 :  12
struct a short :        ffffdc1a :  12 34
struct a int :          ffffdc1c :  00 00 12 34
struct a long :         ffffdc20 :  12 34 56 78
struct a float :        ffffdc24 :  40 13 33 33

struct b :      ffffdc28 :  12 12 34 00 00 12 34 12 34 56 78 40 13 33 33
struct b char :         ffffdc28 :  12
struct b short :        ffffdc29 :  12 34
struct b int :          ffffdc2b :  00 00 12 34
struct b long :         ffffdc2f :  12 34 56 78
struct b float :        ffffdc33 :  40 13 33 33


(3)Intel Pentium4プロセッサ搭載機(Cygwin)
$ gcc -o test_alignment test_alignment.c
$ ./test_alignment.exe
struct a :       22ccd0 :  12 b6 34 12 34 12 00 00 78 56 34 12 33 33 13 40
struct a char :                  22ccd0 :  12
struct a short :         22ccd2 :  34 12
struct a int :           22ccd4 :  34 12 00 00
struct a long :                  22ccd8 :  78 56 34 12
struct a float :         22ccdc :  33 33 13 40

struct b :       22ccc0 :  12 34 12 34 12 00 00 78 56 34 12 33 33 13 40
struct b char :                  22ccc0 :  12
struct b short :         22ccc1 :  34 12
struct b int :           22ccc3 :  34 12 00 00
struct b long :                  22ccc7 :  78 56 34 12
struct b float :         22cccb :  33 33 13 40



どの環境でもcharの1バイトの後に、無駄なパディングがあるか無いかが違うだけ・・・。
事前の予想ではcharの後に3バイト、shortの後に2バイト、もしくはcharの後に7バイト、shortの後に5バイト、intの後に4バイト、longの後に4バイトのパディングを期待していたのだけど。
頭のアドレスは綺麗に8の倍数のアドレスに収まっているので、(事前の予想に近い)何らかの最適化が行われているのは事実なんだろうけど。。。

もうちょっと調べてみないと、ダメかな。




追記:
碧色工房のポインタとメモリと型(構造体)の関係 (2)に詳しく解説がありました。