アンアラインメントデータアクセスについて

  • メモリにデータを格納する際、通常はアラインメントに沿って展開される。ワード単位以上のデータを格納する場合、その開始アドレスがワード単位の整数倍になっていないと、メモリフェッチが余分に発生してスループットが落ちるか、アラインメント違反によってエラーが発生するか、アクセスしたデータが異常となり期待していない挙動を引き起こす。
  • x86では、アラインメントに沿っていないアドレスにアクセスする場合、メモリフェッチが余分に発生するが、アラインメント違反は起こらない。ARM/Linuxでは、アラインメント違反が発生するが、/proc/cpu/alignmentに設定されている値に従って結果として発生する挙動が変わる。
0 例外を無視
1 例外をdmesgに出力
2 例外を捕捉して正常にアクセスする
3 1+2
4 例外を受理してバスエラーを起こす
5 1+4

c.f. Linux Kernel Documentation :: arm : mem_alignment

  • 構造体structは各フィールドの中で最大のアラインメントに併せてパディングを入れてメモリを確保する。ただし、__attribute__((packed)) を指定した場合はパディングを入れない。なお、共用体unionは各フィールドでメモリを共有する。
  • gcc/clangでは__attribute__((aligned(byte)))によってアラインメントをマニュアルで調整することができる。C++11(C++0x)では言語の機能として、alignas(byte)が導入されている。

c.f. ホイール欲しい ハンドル欲しい » C++11 alignas/alignof メモリアライメント

$ cat align.c 
#include <stdio.h>

int main(int argc, char* argv[])
{

    char a[16] = {"abcd"};
    unsigned int *b,*c,*d;
    b = (unsigned int *)(a);
    c = (unsigned int *)(a+2);
    d = (unsigned int *)(a+3);

    printf("char a %s\n",a);
    printf("int b %u\n",*b);
    printf("int c %u\n",*c);
    printf("int d %u\n",*d);

    printf("char a addr %p\n",a);
    printf("int b addr %p\n",b);
    printf("int c addr %p\n",c);
    printf("int d addr %p\n",d);

    struct s1 {
        char i;
    } s1;

    struct s2 {
        int j;
    } s2;

    struct s3 {
        char i;
        int j;
    } s3;

    struct s4 {
        char i;
        int j;
    } __attribute__((packed)) s4;

    union u4 {
        char i;
        int j;
    } u4;

    struct s5 {
    char i;
    __attribute__((aligned(8))) int j;
    }  s5;

    printf("s1 size %zu\n",sizeof(s1));
    printf("s2 size %zu\n",sizeof(s2));
    printf("s3 size %zu\n",sizeof(s3));
    printf("s4 size %zu\n",sizeof(s4));
    printf("u4 size %zu\n",sizeof(u4));
    printf("s5 size %zu\n",sizeof(s5));

    printf("s3 addr %p\n",&s3);
    printf("s3.i addr %p\n",&s3.i);
    printf("s3.j addr %p\n",&s3.j);

    printf("s4 addr %p\n",&s4);
    printf("s4.i addr %p\n",&s4.i);
    printf("s4.j addr %p\n",&s4.j);

    printf("u4 addr %p\n",&u4);
    printf("u4.i addr %p\n",&u4.i);
    printf("u4.j addr %p\n",&u4.j);

    printf("s5 addr %p\n",&s5);
    printf("s5.i addr %p\n",&s5.i);
    printf("s5.j addr %p\n",&s5.j);

    s3.i = 'a';
    s3.j = 65535;

    printf("s3.i %c\n",s3.i);
    printf("s3.j %d\n",s3.j);

    s4.i = 'a';
    s4.j = 65535;

    printf("s4.i %c\n",s4.i);
    printf("s4.j %d\n",s4.j);

    return 0;
}

$ ./a.out 
char a abcd
int b 1684234849
int c 25699
int d 100
char a addr 0x7fff504e5130
int b addr 0x7fff504e5130
int c addr 0x7fff504e5132
int d addr 0x7fff504e5133
s1 size 1
s2 size 4
s3 size 8
s4 size 5
u4 size 4
s5 size 16
s3 addr 0x7fff504e50f0
s3.i addr 0x7fff504e50f0
s3.j addr 0x7fff504e50f4
s4 addr 0x7fff504e50e8
s4.i addr 0x7fff504e50e8
s4.j addr 0x7fff504e50e9
u4 addr 0x7fff504e50e0
u4.i addr 0x7fff504e50e0
u4.j addr 0x7fff504e50e0
s5 addr 0x7fff519cb0b0
s5.i addr 0x7fff519cb0b0
s5.j addr 0x7fff519cb0b8
s3.i a
s3.j 65535
s4.i a
s4.j 65535

$ uname -a
Darwin MBA.local 14.5.0 Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64 x86_64

Thanks to

ARM gcc バッドノウハウ集: アラインメント

データ型のアラインメントとは何か,なぜ必要なのか?

Note : []演算子について

  • 配列arrayと[]演算子に与える添字iにおいて、arrayiは交換可能である。つまり、array[i]i[array]は等価である。これは[]演算子コンパイラ*(array+i)に変換しているためである。
    int i[4] = {1,2,3,4};
    int j=1;

    printf("i[j] %d\n",i[j]);
    printf("j[i] %d\n",j[i]);
    printf("1[i] %d\n",1[i]);
    printf("*(i+j) %d\n",*(i+j));

~~~

i[j] 2
j[i] 2
1[i] 2
*(i+j) 2

30日でできる! OS自作入門

30日でできる! OS自作入門