mallocとdouble

第67回カーネル読書会のビデオと資料」でnoocyte氏が寄せているコメントについて。

malloc() はその仕様上,どんなデータ型にも適合するようアラインされた
アドレスを返さなければならない.
・普通32ビット CPU では,最もアラインメントの厳しいデータ型は double 型
(8バイト・アラインメント) である.
・したがって,malloc() が返すアドレスは8の倍数でなければならない.
私は「それはCの仕様(ISO/IEC 9899:1999)ではないのではないか」とコメントしたのだが、

  • ANSI/ISOでは、アライメントを何バイトにせよという規定はない
  • 実際の処理系では、適切なアライメントを返すように規定・実装されている
  • 32bitのglibcでは、8バイト境界のポインタを返すのが仕様

つまり、ANSI/ISOの仕様ではなくて、glibcの仕様でしょう、ということが言いたかった。
移植性を考える上では、基本的な言語仕様なのか、処理系依存なのかは押さえておく必要がある。
(私のコメントのうち、12バイトを要求すると12バイトきっちり使える領域を返す、という部分は、余分だし、反論としては間違い)

というところで、なぜ8なのかだが、8未満にしない理由は二つあると考えられる。

  1. CPUによってはアクセス違反になる。32bitの場合は4バイト単位なら普通大丈夫。
  2. CPUによってはバスサイクルが増えたり、キャッシュにうまく乗らない。CPUによってまちまちだが、8/16/32バイト境界にすると転送効率が上がる。

アライメント問題は上記の二つのどちらかから生じるが、両方をそれなりに満たそうとすると8や16にするのが現実的だ。
4だとバスサイクルが増えるし、16だとやや大きい。「組み込み型の最大であるdoubleを配置するため」ではなく、「よく使う組み込み型の最大であるdoubleを配置するため」の8なのだと思う。

それと、なぜ規定がないのか(アライメントとは何かという規定自体は存在しているが)の理由を推測してみると、

  1. 「組み込み型で最大のものに合わせる」だとロスが大きくなる可能性がある
    例: GCCだとlong doubleは12バイトだが、12の倍数ではおかしいし、無駄
    例: 256bitの整数型とかをサポートしているかもしれない
  2. 組み込み用途などでは、小さなサイズにしたい
    例: long longをサポートしていても、4バイト単位で使いたい

ということではないだろうか。

そういえばx86浮動小数点ユニットは80bitだったなと思って探したら、「float型とdouble型」のように精度設定機能があるとのこと。知らなかった。

64bit OS と 32bit OS でのデータ型の相違一覧」によると、64bit環境ではlong doubleが16バイトになっている。x86-64だと精度自体は80bitのままだけど、格納領域が64bit単位に丸められているということか。
ただ、「double型 v.s. float型」によると、精度を変えてもあまり速度は変わらないみたい。


組み込み型のサイズを求めるコード。32bit Linux/GCCだと確かにlong doubleが12になった。

#include 
#define PRINT_SIZE(type) printf("size of " #type ": %d\n", sizeof(type))
int main(int argc, char *argv[])
{
PRINT_SIZE(int);
PRINT_SIZE(long);
PRINT_SIZE(double);
PRINT_SIZE(long long);
PRINT_SIZE(long double);
return 0;
}