メモリのアラインメントを確認する


そもそもの話。
なぜ「ゲームプログラマになる前に覚えておきたい技術(以下「セガ本」)」を読んでる途中で「C言語ポインタ完全制覇(以下「ポインタ本」)」を読むことになったか?
それは、セガ本にデータ型のサイズで割りきれるアドレスでデータを読み込まないと、x86系(いわゆるIntel、AMD系)以外のCPUではクラッシュし、x86でもデータを2回読むことになってパフォーマンスが落ちる、と書いてあったから。
で、パッと見て理解できないでいたら、時を同じくしてポインタ本が届き、目次にアラインメントの記述が。
と言うわけで、ポインタ本を読み進めてみた次第。

そこで思ったのが、この本、本当に為になる。
俺みたいなCの見習いの肩もみの家の便所掃除みたいな人間にはいろいろ発見が多すぎて驚く。
例えば

#include <stdio.h>

int main()
{
    int i;
    char s[] = "abcdefghij";

    for ( i = 0; i < sizeof( s ) - 1; ++i ) {
        printf( "%d:%c\n", i, i[s] );
    }
}

なんてコード。
実行するとこう。

McLaren% gcc -o pointer_test pointer_test.c
McLaren% ./pointer_test
0:a
1:b
2:c
3:d
4:e
5:f
6:g
7:h
8:i
9:j

9行目のi[s]はs[i]の間違いと思いがちだけど、”これでも”問題ない。
出来るか出来ないかの話であって、常識的にこの書き方をして良いかは別の話(そしてもちろんダメ)。
この表記はコンパイル時に

printf( "%d:%c\n", i, *( i + s ) );

と変換しているだけ。
確かにポインタ+数値でそのポインタの型の分だけ先へ進む機能がある。
とまぁ、なかなか興味深い。

さてさて本題。
ようやくアラインメントが書いてあるページまでたどり着いた。
どうやら現代のコンパイラはかなりその辺りも配慮しているらしく、次に置かれるデータがそのデータ型で割り切れない場合、”詰め物”をしてくれるんだとか。
実際コードにして試してみまふ。

// alignment_check.c
#include <stdio.h>

typedef struct {
    char ch;
    double dn;
    short sn;
    int nu;
} MyStruct;

int main()
{
    MyStruct hoge;
    unsigned long gap1, gap2, gap3;

    printf( "sizeof char\t%lu\n", sizeof( char ) );
    printf( "sizeof double\t%lu\n", sizeof( double ) );
    printf( "sizeof short\t%lu\n", sizeof( short ) );
    printf( "sizeof int\t%lu\n\n", sizeof( int ) );

    printf( "sizeof MyStruct\t%lu\n\n", sizeof( MyStruct ) );

    printf( "\t&hoge's hexadecimal memory address.\n" );
    printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
    printf( "\t&hoge\t\tMyStruct\t%p\t%4lu\n", &hoge, sizeof( hoge ) );

    printf( "\t&hoge.ch\tchar\t\t%p\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
    gap1 = (unsigned long)&hoge.dn -
        ( (unsigned long)&hoge.ch + sizeof( hoge.ch ) );
    printf( "gap1:%lu\n", gap1 );

    printf( "\t&hoge.dn\tdouble\t\t%p\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
    gap2 = (unsigned long)&hoge.sn -
        ( (unsigned long)&hoge.dn + sizeof( hoge.dn ) );
    printf( "gap2:%lu\n", gap2 );

    printf( "\t&hoge.sn\tshort\t\t%p\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
    gap3 = (unsigned long)&hoge.nu -
        ( (unsigned long)&hoge.sn + sizeof( hoge.sn ) );
    printf( "gap3:%lu\n", gap3 );

    printf( "\t&hoge.nu\tint\t\t%p\t%4lu\n\n", &hoge.nu, sizeof( hoge.nu ) );

    printf( "char + gap1 + double + gap2 + short + gap3 + int = %lu\n\n",
            sizeof( char ) + gap1 +
            sizeof( double ) + gap2 +
            sizeof( short ) + gap3 +
            sizeof( int ) );

    printf( "&hoge's decimal memory address (with alighnment gap size).\n" );
    printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
    printf( "\t&hoge\t\tMyStruct\t%ll\t%4lu\n", &hoge, sizeof( hoge ) );

    printf( "\t&hoge.ch\tchar\t\t%lu\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
    printf( "gap1:%lu\n", gap1 );

    printf( "\t&hoge.dn\tdouble\t\t%lu\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
    printf( "gap2:%lu\n", gap2 );

    printf( "\t&hoge.sn\tshort\t\t%lu\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
    printf( "gap3:%lu\n", gap3 );

    printf( "\t&hoge.nu\tint\t\t%lu\t%4lu\n", &hoge.nu, sizeof( hoge.nu ) );

    return 0;
}

まぁ、こんだけ親切に情報盛ったから、アドレス表記だけで良いとは思ったけど、一応アドレスを10進数にしたやつも記載。
(つっても、64ビット環境においてunsigned long程度でアドレス表示して間に合わない気がするけど、ギャップを目視することが目的だから、あえてこのまま。
あれ?ラップトップのMacでgcc動かしたときって64ビット扱い?
・・・ま、今回は気にしないことに。
どうせテストだし。。。
まともに64ビットアドレス書いてたら、京単位らしいから・・・ね。)

で、結果。

McLaren% gcc -o alignment_check alignment_check.c
alignment_check.c: In function ‘main’:
alignment_check.c:48: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘struct MyStruct *’
alignment_check.c:49: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘char *’
alignment_check.c:52: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘double *’
alignment_check.c:55: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘short int *’
alignment_check.c:58: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘int *’
McLaren% ./alignment_check
sizeof char     1
sizeof double   8
sizeof short    2
sizeof int      4

sizeof MyStruct 24

        &hoge's hexadecimal memory address.
        name            type            address         size
        &hoge           MyStruct        0x7fff5fbfef10    24
        &hoge.ch        char            0x7fff5fbfef10     1
gap1:7
        &hoge.dn        double          0x7fff5fbfef18     8
gap2:0
        &hoge.sn        short           0x7fff5fbfef20     2
gap3:2
        &hoge.nu        int             0x7fff5fbfef24     4

char + gap1 + double + gap2 + short + gap3 + int = 24

&hoge's decimal memory address (with alighnment gap size).
        name            type            address         size
        &hoge           MyStruct        140734799802128   24
        &hoge.ch        char            140734799802128    1
gap1:7
        &hoge.dn        double          140734799802136    8
gap2:0
        &hoge.sn        short           140734799802144    2
gap3:2
        &hoge.nu        int             140734799802148    4

こんな塩梅。
ワーニングは全部アドレスを無理矢理10進数で出したため。
「ポインタ本」と同じように構造体で確認した。
こうすれば普通の処理系では間に別の変数を入れたりする余地はあるまい:-)
で、確かにアドレスを次のメンバ変数のサイズで割り切れない場合、ギャップが出ている。
なるほど、こうすればどのCPUでもスマートに動くというわけですかい。
勉強になった。

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

著者/訳者:前橋 和弥

出版社:技術評論社( 2001-01 )

定価:¥ 2,394

Amazon価格:¥ 2,394

単行本 ( 323 ページ )

ISBN-10 : 4774111422

ISBN-13 : 9784774111421



ゲームプログラマになる前に覚えておきたい技術

著者/訳者:平山 尚(株式会社セガ)

出版社:秀和システム( 2008-11-15 )

定価:¥ 4,725

Amazon価格:¥ 4,725

単行本 ( 872 ページ )

ISBN-10 : 4798021180

ISBN-13 : 9784798021188


Post to Twitter

, , , ,

Comments are closed.

Bad Behavior has blocked 24 access attempts in the last 7 days.