境界調整要求を調べる

こんにちは、めのんです!

前回予告したとおり、今回は境界調整要求を調べる方法について解説します。
境界調整については以前解説しましたので、まだよくわからない方はそちらを参照してくださいね。

境界調整要求って?

境界調整というのは、メモリ上にデータを配置するアドレスが2や4や8の倍数でなければならないというものでした。
では「境界調整要求」というのは何かというと、データをnの倍数アドレスに配置しないといけないのであれば、その「n」のことです。

整数型なんかだと、その型のサイズと同じ場合が多いんですけど、構造体のような型だと簡単にはわかりませんね。

普通に簡単なプログラミングをやっているだけならそんなのは必要ないことがほとんどだと思います。
でも、ちょっと凝ったプログラム、もう少し正確にいうとシステムプログラミングをするなら絶対必要になってきます。

予断になりますけど、システムプログラミングをしないのならCを使う必要はないと思うんですよ。
逆にいえば、せっかくCを学ぶのであればシステムプログラミングに関わらないのはもったいないですよ!

境界調整要求を実際どんな用途に使うのかは、この週末に紹介できたらなと考えています。

境界調整要求の求め方

境界調整要求を求めるにはいくつかの方法があります。
いくつかの方法といっても、どんな処理系でも使える方法というのはひとつしかありません。
あとはCの規格バージョンや処理系の独自拡張に依存するものです。

C11以降の方法

C11以降には「_Alignof演算子」というのがあります。
「stdalign.h」ヘッダをインクルードすれば_Alignof演算子に「alignof」という別名が定義されています。

使い方はこんな感じですね。

size_t n = _Alignof(int);

_Alignof演算子のオペランドは型名です。
不完全型をオペランドにするとコンパイルエラーになってしまいますので注意してください。
要素数を指定していない配列型なら境界調整要求は決まりそうなものなんですけど無理みたいですね。

上のサンプルでもsize_t型のオブジェクトに結果を格納しているように、_Alignof演算子の評価結果はsize_t型になります。

GCCやClangの独自拡張

GCCやClangには独自拡張の機能として「__alignof__演算子」というのがあります。
この演算子は結構な優れもので、sizeof演算子と同じようにオペランドに型名だけじゃなくて式を与えることもできます。

使い方はこんな感じです。

size_t m = __alignof__(int);
size_t n = __alignof__(123);

__alignof__演算子の評価結果も_Alignof演算子と同じでsize_t型になります。

Visual C++の独自拡張

Visual C++には独自拡張の機能として「_alignof演算子」「__alignof演算子」があります。
マイクロソフト社のドキュメントには下線がない「alignof演算子」が使えるような書き方をされていますが、試してみるとコンパイルエラーになってしまいます。

機能としてはC11以降の_Alignof演算子と同じみたいです。
評価結果の型もsize_t型で同じです。

規格のバージョンや処理系に依存しない方法

最後になりましたがこれが本命です。
でも、C11以降なら_Alignof演算子を使ってくださいね。

それでは具体的なコードを見てみましょう。
int型の境界調整要求を求めるには次のようにします。

size_t n = offsetof(struct { char a; int b; }, b);

offsetofは第1引数に構造体の型を、第2引数に構造体のメンバ名を渡します。
評価結果は指定した構造体の先頭から何バイトの位置にメンバが配置されるかを返します。

たとえば、

struct A
{
  int a;
  int b;
};
size_t n = offsetof(struct A, b);

上のように書いたとします。
int型のサイズが4バイトだとすれば、bは先頭から4バイトの位置に配置されますね。
offsetofはその値(=4)を返します。

offsetofは「stddef.h」ヘッダで定義されているので必ずインクルードしてください。

この方法はちょっとトリッキーなのでもう少し解説が必要ですね。
まず、使い方なんですけど、さっきのコードだけだとわかりにくいと思いますので、境界調整要求求めたい型をソースコード上でも「型」として書き直してみます。

size_t n = offsetof(struct { char a; 型 b; }, b);

配列型の場合はそれなりの書き方にしてくださいね。
こんな風にです。

size_t n = offsetof(struct { char a; int b[10]; }, b);

では、こんなコードでどうして要求調整数を求めることができるのでしょうか?
このコードでは、

struct
{
  char a;
  型 b;
};

という構造体で、メンバbが構造体の先頭から何バイト目に配置されるかを求めています。

もし型の境界著整要求が1であれば、メンバaのあとには詰め物が入りません。
この場合はメンバbの前にはメンバaしかありませんからoffsetofの結果は1になります。

型の境界調整要求が2であれば、メンバbの前には詰め物が1バイト入ります。
今度はメンバbの前には1バイトのメンバaと1バイトの詰め物がありますのでoffsetofの結果は2になります。

同じように考えれば、型の境界調整要求が4や8の場合でもうまくいくことが理解できるはずです。
ぜひご自身でも確認してみてくださいね。


それでは今回の解説は以上となります。
どうして境界調整要求なんか求める必要があるんだろうと思われた方は、この週末に予定している記事を必ず読んでくださいね。