我们假设结构体定义如下所示:
- #include <stdio.h>
- #include <stdlib.h>
- struct test_s
- {
- int pad1;
- int pad2;
- int pad3;
- int pad4;
- int pad5;
- };
思路1: 非常简单,直接用地址差值即可求得。
- int main(int argc, char *argv[])
- {
- struct test_s sss;
- struct test_s *t = &sss;
- printf("%d\n", (int)&(t->pad2));
- return 0;
- }
思路2: 考虑宏定义的实现。(最佳思路!)
ANSI C标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此( (test_s*)0 )的结果就是一个类型为test_s*的NULL指针。如果利用这个NULL指针来访问s的成员当然是非法的,但&(((test_s*)0)->m)的意图并非想存取test_s字段内容,而仅仅是计算当结构体实例的首址为((s*)0)时m字段的地址。聪明的编译器根本就不生成访问m的代码,而仅仅是根据test_s的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。
- #define OFFSET(Type, member) (size_t)&( ((Type*)0)->member) )
如上做法避免了一定要实例化一个结构体对象,并且求值是在编译期进行,没有运行期负担。因此是该问题的首选方案。
- int main(int argc, char *argv[])
- {
- size_t offset = OFFSET(test_s, pad4);
- return 0
- }
实际上这种利用编译器掌握的整个程序的信息以在编译期计算某些值的方法与现在C++编程中很流行的(静态)元编程技术类似,只不过C++程序员可以利用模板技术在编译期完成非常复杂的计算,而缺乏模板支持的 ANSI C在这方面的能力则要弱许多。
C++ 的实现:
- /* Define offsetof macro */
- #ifdef __cplusplus
- #ifdef _WIN64
- #define offsetof(s,m) (size_t)( (ptrdiff_t)&reinterpret_cast<const volatile char&>((((s *)0)->m)) )
- #else
- #define offsetof(s,m) (size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))
- #endif
- #else
- #ifdef _WIN64
- #define offsetof(s,m) (size_t)( (ptrdiff_t)&(((s *)0)->m) )
- #else
- #define offsetof(s,m) (size_t)&(((s *)0)->m)
- #endif
- #endif /* __cplusplus */