スタックとメモリアライメント

以前、__int128型というのが使われたプログラムがあって、

コンパイラによっては128bit整数型をそういう名前で持ってることがあるらしい。

でも、使用しているマイコンのコンパイラにはそういう型はない。

困ったなぁ……と思ったが、よく調べると128bitの整数を使いたくて使っていたわけではなかった。

目的はスタックとして16byteアライメントされた領域を確保するためだった。

というわけで、こういう型を定義して回避した。

typedef struct{ _Alignas(16) char b[16]; } __int128;

_AlignasはC11(2011年版のC言語)で導入された修飾子で、

これを付けると構造体のメンバーを指定のバイト数でアライメントできる。


N-byteアライメントというのはメモリアドレスをNの倍数に並べることを言う。

多くのコンピュータでは4byte型のアドレスは4の倍数から始まることが必須となっている。

__int128型(16byte)の変数は16の倍数のアドレスに配列されることを利用して、

16byteアライメントされたスタックを確保しようとしたわけである。

普通はアライメントについてはあまり意識しなくても問題ないのだけど、

char型の配列を4個連結して4byte型としてアクセスするような場合は、

char型は1byteだけど、配列の先頭アドレスは4byteアライメントされてないと困る。

このような特別なアライメントが必要な場合には_Alignasを付けて宣言すればよいというわけ。


そもそもスタックが16byteアライメントされてなければいけないというのは、

どうもこのマイコンの取り決めとしてそうなっているらしい。

スタックポインタ(sp)は常に16の倍数でなければならないと規定しているらしい。

スタック上に保存するデータが4byteだけとしてもspは16進めないといけないと。


この規定があることで、スタックに保存するデータについて2,4,8,16byteアライメントすることができる。

スタックに16byteアライメントされたデータを保存するシチュエーションはよくわからないが、

必要ならばそういうこともできるようになっているようで、

スタックに保存されるローカル変数に _Alignas(16) と付けることはできた。

それ以上のバイト数、例えば _Alignas(32)と付けるとちゃんとエラーになった。

あくまでもスタックに配置する場合の制約ですが。


8byteアライメントまではデータ型の都合で使われることが多いが、

それ以上となるとキャッシュライン合わせで使われるのかな。

メモリキャッシュは64byteなどのキャッシュライン単位でデータの読み書きが行われる。

同一キャッシュラインに他のデータが混ざると不都合があるので、

_Alignas(64)を付けて宣言することで、同一キャッシュラインに他のデータが入らないようにするとか。

16byte以上のアライメントを使うというのはそういうシチュエーションかなと。


C11の _Alignas という構文は知らない人も多いかも。

昔からコンパイラの独自構文でこういうのはあったと思うけど……

統一的な表現が導入されたことで移植性が高まることになる。

コンパイラの説明書にアライメントを指定する独自構文が書いてなくて、

あれ? と思ったら他の章にC11の_Alignasのことが言及されていたんですよね。

Cの標準的な構文で対応できるようになったので独自拡張をやめたんだろうな。

そういうこともある。