C offsetof()的这个实现为什么有效?
在ANSI C中,offsetof的定义如下C offsetof()的这个实现为什么有效?,c,pointers,offsetof,C,Pointers,Offsetof,在ANSI C中,offsetof的定义如下 #define offsetof(st, m) \ ((size_t) ( (char *)&((st *)(0))->m - (char *)0 )) 既然我们正在取消对空指针的引用,为什么这不会引发分段错误?或者这是某种编译器黑客,它看到只有偏移量的地址被取出,所以它静态地计算地址,而实际上不去引用它?这段代码是否可移植?在上述代码中,没有任何一点是未引用的。当对地址值使用*或->查找引用值时,会发生解引用。上述*的唯一用
#define offsetof(st, m) \
((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
既然我们正在取消对空指针的引用,为什么这不会引发分段错误?或者这是某种编译器黑客,它看到只有偏移量的地址被取出,所以它静态地计算地址,而实际上不去引用它?这段代码是否可移植?在上述代码中,没有任何一点是未引用的。当对地址值使用
*
或->
查找引用值时,会发生解引用。上述*
的唯一用途是在类型声明中进行转换
上面使用了->
运算符,但它不用于访问值。相反,它用于获取值的地址。这是一个非宏代码示例,应该可以让它更清晰一些
SomeType *pSomeType = GetTheValue();
int* pMember = &(pSomeType->SomeIntMember);
第二行实际上不会导致解引用(依赖于实现)。它只返回pSomeType
值中SomeIntMember
的地址
您看到的是任意类型和字符指针之间的大量转换。使用char的原因是,它是C89标准中唯一(可能是唯一)具有显式大小的类型之一。尺寸是1。通过确保大小为1,上述代码可以实现计算值的真实偏移量的邪恶魔法 它不会出错,因为您没有取消引用它。指针地址被用作从另一个数字中减去的数字,而不是用来寻址内存操作。它计算成员
m
相对于类型为st
的对象表示的起始地址的偏移量
((st*)(0))
引用类型为st*
的NULL
指针。
&((st*)(0))->m
指此对象中成员m的地址。由于此对象的起始地址是0(NULL)
,因此成员m的地址正好是偏移量
char*
转换和差值计算偏移量(以字节为单位)。根据指针操作,当您在两个T*
类型的指针之间进行差异时,结果是操作数包含的两个地址之间表示的T
类型的对象数。在ANSI C中,offsetof
不是这样定义的。它没有这样定义的原因之一是某些环境确实会抛出空指针异常,或者以其他方式崩溃。因此,ANSIC将offsetof()
的实现留给编译器构建者
上面显示的代码通常适用于不主动检查空指针的编译器/环境,但仅当从空指针读取字节时才会失败 回答问题的最后一部分,代码不可移植
只有当两个指针指向同一数组中的对象或指向数组最后一个对象(7.6.2加法运算符,H&S第五版)时,两个指针相减的结果才是可定义的和可移植的。尽管这是
offsetof
的典型实现,但该标准并未强制要求,只是说:
以下类型和宏在标准标题
[…]中定义
偏移(
类型
,
成员标识符
)
它扩展为一个整型常量表达式,其类型为size\t
,值为
它是结构成员(由成员标识符指定)的偏移量(以字节为单位),
从其结构的开始(由类型指定
)。类型和成员标识符
应确保
静态
类型
t代码>
然后表达式&(t.
成员指示符
)
计算为地址常量。(如果指定的成员是位字段,则行为未定义。)
阅读p J Plauger的“标准C库”,讨论它和
中的其他项目,这些都是边界线功能,可能(应该?)使用正确的语言,可能需要特殊的编译器支持
它只具有历史意义,但我在386/IX上使用了早期的ANSI C编译器(参见,我告诉过你历史意义,大约在1990年),该编译器在该版本的offsetof
上崩溃,但当我将其修改为:
#define offsetof(st, m) ((size_t)((char *)&((st *)(1024))->m - (char *)1024))
这是一种编译器错误,尤其是因为头是与编译器一起分发的,不起作用。清单1:一组具有代表性的offsetof()
宏定义
// Keil 8051 compiler
#define offsetof(s,m) (size_t)&(((s *)0)->m)
// Microsoft x86 compiler (version 7)
#define offsetof(s,m) (size_t)(unsigned long)&(((s *)0)->m)
// Diab Coldfire compiler
#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))
typedef struct
{
int i;
float f;
char c;
} SFOO;
int main(void)
{
printf("Offset of 'f' is %zu\n", offsetof(SFOO, f));
}
宏中的各种运算符的求值顺序如下:
((s*)0)
接受整数零并将其强制转换为指向s
的指针
((s*)0)->m
取消指向结构成员的指针的引用m
&((s*)0)->m)
计算m
的地址
(size_t)和((s*)0)->m)
将结果强制转换为适当的数据类型李>
根据定义,结构本身位于地址0处。因此,指向的字段地址(上面的步骤3)必须是从结构开始的偏移量,以字节为单位。引用C标准中的偏移量的宏:
C标准第6.6节第9段
地址常量是空指针、指向指定静态存储持续时间对象的左值的指针或指向函数指示符的指针;应使用一元&
运算符或转换为指针类型的整数常量显式创建,或使用数组或函数类型的表达式隐式创建。数组下标[]
和成员访问
和->
运算符,地址&
#define offsetof(type, member) ((size_t)&((type *)0)->member)