offsetof
読み:オフセット-オブ
外語:offsetof

 C/C++で、構造体内にあるメンバーのオフセットを返す機能。多くの場合マクロとして実装される。
目次

書式

C
#include <stddef.h>
size_t offsetof(type, member);

C++
#include <cstddef>
size_t offsetof(type, member);

準拠

機能
 offsetof() は、構造体 type 中にあるフィールド member の、構造体先頭からのオフセットを size_t 型で返す。
 オフセットはポインター差分に相当すると思われるが、ptrdiff_t 型などではなく、なぜか size_t 型で返す仕様となっている。

目的
 構造体は、仕様変更等でメンバーの追加や削除が発生することがある。
 そうでなくても、コンパイラーによっては、環境にあわせてフィールド間にパディングを挿入したりフィールドの順序を入れ替えたりすることもあるため、メモリー空間への配置については実装により変動する。
 このような現状から、フィールドのオフセットを定数として決め打ちすることは危険である。オフセットが必要な状況においては、このoffsetof機能は有用である。
 なお、member がビットフィールドの場合は使用できず、コンパイル時点でエラーとなる。

定義

gcc 4.4
 Linuxのユーザー空間で、gcc 4.4の場合は、次のように定義されている。
 /usr/lib/gcc/x86_64-linux-gnu/4.4/include/stddef.h
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
 GCC 3.5から追加された独自の組み込み関数 __builtin_offsetof() を用いている。

Linux
 Linuxカーネルでは、次のようにして使う。
#incluce <linux/stddef.h>
 定義は、/usr/src/linux-headers-2.6.XX-XX/include/linux/stddef.h などにある。
 AndroidのLinuxカーネルでも external/kernel-headers/original/linux/stddef.h に存在し、offsetof の定義は全く同じ内容である。
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
 __compiler_offsetof()は、__builtin_offsetof()の別名である。
#define __compiler_offsetof(a,b) __builtin_offsetof(a,b)

Visual C++ 6.0
 Visual C++ 6.0では、STDDEF.H で次のように定義されている。
#define offsetof(s,m)   (size_t)&(((s *)0)->m)

Visual C++ 2013
 Visual C++ 12.0では、stddef.h で次のように定義されている。
/* Define offsetof macro */
#ifdef __cplusplus

#ifdef _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&reinterpret_cast<const volatile char&>((((s *)0)->m)) )
#else  /* _WIN64 */
#define offsetof(s,m)   (size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))
#endif  /* _WIN64 */

#else  /* __cplusplus */

#ifdef _WIN64
#define offsetof(s,m)   (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else  /* _WIN64 */
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif  /* _WIN64 */

#endif  /* __cplusplus */
 さすがMicrosoftだけあって、(ptrdiff_t) でないとおかしいということに気づいた記述になっている。またC++でビルドすると、const volatile char&でreinterpret_castするようになっている。

計算の正体
 オフセットを計算によって求める一般的な方法は、次の手法である。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
 なぜこれでオフセットが求まるのかを説明する。
 オフセットとは基準からの差のことであり、求めたいメンバーのアドレスから、その構造体の先頭アドレスを引き算すればよいだけである。しかし、「その構造体の先頭アドレス」が0であれば、引き算が簡単である。そこで、このような難解な式に至った。
 この式の実質的な処理は以下のみである。
(size_t) &((TYPE *)0)->MEMBER
 そしてこの式は、いきなりメインイベントから始まる。
(size_t) &((TYPE *)0)->MEMBER
 カッコの中のこの部分で、整数0を、入力された構造体名でキャストしてポインターとしている。これによって、このカッコ内には、アドレス0に配置された構造体のポインターが誕生する。
 次に、& と -> では -> の方が優先順位が高いので、-> の演算が始まる。
(size_t) &((TYPE *)0)->MEMBER
 この部分は、構造体が配置されるアドレスに関わらず、先の構造体中のメンバーが指し示される。
 最後に、アドレス取得演算子の演算が始まる。
(size_t) &((TYPE *)0)->MEMBER
 この演算子の対象範囲はここ。
(size_t) &((TYPE *)0)->MEMBER
 この -> 演算子により、構造体中のメンバーのポインターを指し示す。
 さてこの構造体だが、最初の定義によりアドレス0に配置されている。構造体のアドレスが今回は0と特別に分かっている。-> によって配置アドレスが判明するが、構造体の先頭アドレスが 0 なので、この内容は実質的にオフセットと等価となる。
(size_t) &((TYPE *)0)->MEMBER
 最終的に、size_t でキャストして整数になって返却される。

再検索