#define MY_OFFSET(struc, e) (size_t)&(((struc *)0)->e) //得到地址为0的对象的成员e的地址,此时即是偏移量。
上面定义的MY_OFFSET宏就是要的MyField的偏移。这样强制转换后的结构指针怎么可以用来访问结构体字段?其实这个表达式根本没有也不打算访问MyField字段。ANSI C标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此((MyStruct*)0)的结果就是一个类型为MyStruct*的NULL指针。如果利用这个NULL指针来访问MyStruct的成员当然是非法的,但&(((MyStruct*)0)->MyField)的意图并非想存取MyField字段内容,而仅仅是计算当结构体实例的首址为((MyStruct*)0)时MyField字段的地址。聪明的编译器根本就不生成访问MyField的代码,而仅仅是根据MyStruct的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。又因为首址的值为0,所以这个地址的值就是字段相对于结构体基址的偏移。
如上做法避免了一定要实例化一个MyStruct对象,并且求值是在编译期进行,没有运行期负担。实际上这种利用编译器掌握的整个程序的信息以在编译期计算某些值的方法与现在C++编程中很流行的(静态)元编程技术类似,只不过C++程序员可以利用模板技术在编译期完成非常复杂的计算,而缺乏模板支持的ANSI C在这方面的能力则要弱许多。
由此思想,我们还可以求出MyField的长度, #define MY_SIZE(struc,e) sizeof(((struc*)0)->e)
在inside the c++ object model中看到了另一种求法:#define MY_OFFSET(struc, e) &struc::e
用这种方法的话,显示结果就必须使用printf。printf("%d",MY_OFFSET(A,m_a));
你使用cout<<MY_OFFSET(A,m_a); 你使用单步跟踪的话,你可以发现调用的函数是_Myt& operator<<(_Bool _X),后面不就不多解释了,大家也明白了吧。