gccで指定できるアラインメントのサイズには限界がある
C言語におけるデータのアラインメントについての小ネタです.
アラインメント自体については,
データ型のアラインメントとは何か,なぜ必要なのか?
にとても詳しいです.
問題設定
4バイトの変数を16個の配列で確保するとします.
int a[16];
もし各レベルキャッシュのキャッシュラインサイズが64バイトなら,
この配列aを1ラインに収めることができるはずです.
事例1: 配列サイズを定数で指定
__atribute__ ((aligned(64))) を使って,アラインメントを64バイト境界に指定してみましょう.
alignment_stack_const.c
#include <stdio.h> #include <stdint.h> int main() { uintptr_t addr; int __attribute__ ((aligned(64))) a[16]; addr = (uintptr_t)a; printf("a = %p\n", (void *)a); printf("%p %% 64 == %d\n", (void *)a, addr % 64); return 0; }
(ポインタを整数に変換する部分は Cのポインタを整数に変換する - bkブログ を参照)
これをコンパイルして実行すると,aは確かに64バイト境界にアラインされていることが分かります.
$ gcc alignment_stack_const.c $ ./a.out a = 0xbffd3c40 0xbffd3c40 % 64 == 0 $ ./a.out a = 0xbf80ef40 0xbf80ef40 % 64 == 0 $ ./a.out a = 0xbfa08640 0xbfa08640 % 64 == 0 ...
事例2: 配列サイズを変数で指定
比較的新しいCの規約では,配列長を変数で指定することができます.
この機能を使って配列を確保してみます.
alignment_stack_var.c
#include <stdio.h> #include <stdint.h> int main() { uintptr_t addr; int n = 16; int __attribute__ ((aligned(64))) a[n]; addr = (uintptr_t)a; printf("a = %p\n", (void *)a); printf("%p %% 64 == %d\n", (void *)a, addr % 64); return 0; }
先程のソースとは,「16」という配列長を「n」という変数を介して指定している部分以外変わりませんが,
これをコンパイルして実行すると,64バイト境界にアラインメントがとれない場合があります.
*1
*2
$ gcc alignment_stack_var.c $ ./a.out a = 0xbf8e5130 0xbf8e5130 % 64 == 48 $ ./a.out a = 0xbfb98d60 0xbfb98d60 % 64 == 32 $ ./a.out a = 0xbfd47d80 0xbfd47d80 % 64 == 0 ...
何に気をつければよいか
実はアラインメントのサイズには,コンパイラ・リンカが定める最大値があります.
gcc の定義済みマクロ, "__BIGGEST_ALIGNMENT__" を確認することで,
お使いの環境での最大値が分かります.
$ cpp -dD alignment_stack_var.c |grep __BIGGEST_ALIGNMENT__ #define __BIGGEST_ALIGNMENT__ 16
自分の環境では __BIGGEST_ALIGNMENT__ の値が 16 になっていたため,
いつでも64バイトのアラインメントがとれることを期待してはいけないようでした.
一方で, alignment_stack_var.c をコンパイルして実行すると,最低でも16バイト境界にアライン
されていることは確認できるはずです.