在ANSI C中,offsetof定义如下.
#define offsetof(st, m) \ ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
为什么这不会引发分段错误,因为我们正在取消引用NULL指针?或者这是某种编译器黑客,它看到只有偏移的地址被取出,所以它静态地计算地址而不实际解除引用它?这个代码也可以移植吗?
在上面的代码中没有任何一点被解除引用.当*
或->
在地址值上使用以查找引用值时,会发生取消引用.*
上面的唯一用法是用于铸造目的的类型声明.
在->
操作上面使用,但它不是用来访问值.相反,它用于获取值的地址.这是一个非宏代码示例,应该使它更清晰一点
SomeType *pSomeType = GetTheValue(); int* pMember = &(pSomeType->SomeIntMember);
第二行实际上不会导致取消引用(依赖于实现).它只返回值SomeIntMember
内的地址pSomeType
.
你看到的是在任意类型和char指针之间进行大量的转换.char的原因是它是C89标准中唯一具有明确大小的类型(可能是唯一的)类型之一.大小为1.通过确保大小为1,上面的代码可以做计算值的真实偏移的邪恶魔法.
在ANSI C中,offsetof
没有像那样定义.它没有被定义的原因之一是某些环境确实会抛出空指针异常,或者以其他方式崩溃.因此,ANSI C将offsetof( )
开放的实现留给编译器构建器.
上面显示的代码对于没有主动检查NULL指针的编译器/环境是典型的,但只有在从NULL指针读取字节时才会失败.
虽然这是一个典型的实现offsetof
,但标准没有强制要求,它只是说:
标准头文件中定义了以下类型和宏
[...]
offsetof(
type
,
member-designator
)
它扩展为一个整数常量表达式
size_t
,该表达式具有类型,其值是以字节为单位的偏移量member-designator
,从结构的开头(由其指定)到结构成员(由其指定type
).类型和成员指示符应为给定的
static
type
t;
然后表达式求值为地址常量.(如果指定的成员是位字段,则行为未定义.)
&(t.
member-designator
)
阅读PJ Plauger的"标准C库",讨论它和其他
所有边界线功能的项目,这些功能可以(应该?)使用正确的语言,并且可能需要特殊的编译器支持.
这只是历史性的兴趣,但是我在386/IX上使用了早期的ANSI C编译器(参见,我告诉过您的历史兴趣,大约在1990年),offsetof
当我将其修改为:
#define offsetof(st, m) ((size_t)((char *)&((st *)(1024))->m - (char *)1024))
这是一种编译错误,不仅仅是因为标头随编译器一起分发而且不起作用.
要回答问题的最后部分,代码不可移植.
只有当两个指针指向同一个数组中的对象或指向一个超过数组最后一个对象的对象时,才能定义和删除两个指针的结果(7.6.2 Additive Operators,H&S Fifth Edition)