MSDN Flash 2009/8/11 No.330 の答え - (2)

もう先月の話になるのですね…2つ目のコードの問題について解説したいと思います。
掲載したコードは以下の通り

 char s[10];
memset(s, 0, 10);
double *val = (double*) (&s[2]);
printf("%lf\n", *val);

期待される実行結果は x86, x64環境では 0.000000 と表示、Itanium環境では整列違反例外によるプログラムの異常終了です。

これを一度に Itaniumでのアセンブリに展開すると話がややこしくなるので、

char s[10];
double *val;
double val2;
memset(s, 0, 10);
val = (double*) (&s[2]);
val2 = *val; // 問題の部分
printf("%lf\n", val2);

と書き直すことにします。

このコードをコンパイルすると

{   .mib  //R-Addr: 0X01e0
    adds    r29=val$, sp                    //6     cc:0
    adds    r27=val2$, sp                    //6     cc:0
    nop.b     0;;
}
{   .mmi  //R-Addr: 0X01f0
    ld8    r28=[r29];;                    //6     cc:1
    ldfd    f6=[r28]                    //6     cc:3
    nop.i     0;;
}
{   .mmi  //R-Addr: 0X0200
    stfd    [r27]=f6;;                    //6     cc:9
    adds    r26=val2$, sp                    //7     cc:0
    addl    r25=@gprel(__imp_printf#),gp            //7     cc:0
}

となります。

val2 = *val; が

ld8 r28=[r29];;
ldfd f6=[r28]

と展開されていることがわかります。しかし、この r29 は 16-bytes aligned を仮定された char s[10] の 2-bytes目を指しているので miss-alignedとなります。
Itaniumでは参照されるアドレスは自然長に整列されていなければなりません。この場合、8-bytes alignmentでなければならないため整列違反例外となるわけです。

このように、渡された型が自然長に整列されているかどうかわからない場合は __unaligned キーワードをつけることで問題を回避することができます。2行目を

__unaligned double *val;

と書き換えると、コンパイルの結果は

{   .mmi  //R-Addr: 0X01e0
    adds    r29=val$, sp;;                    //6     cc:0
    ld8    r28=[r29]                    //6     cc:1
    adds    r29=val2$, sp;;                    //6     cc:1
}
{   .mib  //R-Addr: 0X01f0
    mov    r27=r28                        //6     cc:2
    adds    r26=1, r28                    //6     cc:2
    nop.b     0;;
}
{   .mmb  //R-Addr: 0X0200
    ld1    r25=[r27], 2                    //6     cc:3
    ld1    r22=[r26], 2                    //6     cc:3
    nop.b     0;;
}
{   .mmi  //R-Addr: 0X0210
    ld1    r20=[r27], 2                    //6     cc:4
    ld1    r18=[r26], 2                    //6     cc:4
    dep    r21=r22, r25, 8, 8;;                //10     cc:4
}
{   .mmi  //R-Addr: 0X0220
    ld1    r16=[r27], 2                    //6     cc:5
    ld1    r11=[r26], 2                    //6     cc:5
    dep    r19=r20, r21, 16, 8;;                //6     cc:5
}
{   .mmi  //R-Addr: 0X0230
    ld1    r9=[r27]                    //6     cc:6
    ld1    r31=[r26]                    //6     cc:6
    dep    r17=r18, r19, 24, 8;;                //6     cc:6
}
{   .mii  //R-Addr: 0X0240
    nop.m     0
    dep    r15=r16, r17, 32, 8;;                //6     cc:7
    dep    r10=r11, r15, 40, 8;;                //6     cc:8, 00000028H
}

{   .mii  //R-Addr: 0X0250
    nop.m     0
    dep    r8=r9, r10, 48, 8;;                //6     cc:9, 00000030H
    dep    r30=r31, r8, 56, 8;;                //6     cc:10, 00000038H
}
{   .mmi  //R-Addr: 0X0260
    setf.d    f6=r30;;                    //6     cc:11
    stfd    [r29]=f6                    //6     cc:17
    nop.i     0;;
}

となり、このコードは問題なく動作するようになります。(しかし、長くて読む気にならないのは気のせいでしょうか…)

違いは ld8 が 8個の ld1 に置き換えられているところです。つまり、double val; を構成する全ての領域を 1-byteずつ読み込んでいることになります。

問題なく動作するようになったとしても、3-cyclesで完了する処理に 9-cyclesもかけることになるわけですから処理は遅くなります。

 

x86/x64 においても、自然長への整列がなされていない場合はキャッシュヒットに対するペナルティが課せられるため動作が低下します。

このように、自然長に配置されていないスタックや構造体を用いる場合には十分に注意しましょう。