C 如何使用“offsetof”以符合标准的方式访问字段?
假设我有一个结构并将偏移量提取到一个成员:C 如何使用“offsetof”以符合标准的方式访问字段?,c,pointers,language-lawyer,offsetof,C,Pointers,Language Lawyer,Offsetof,假设我有一个结构并将偏移量提取到一个成员: struct A { int x; }; size_t xoff = offsetof(A, x); 给定一个指向struct a的指针,如何以标准一致性方式提取成员?当然,假设我们有一个正确的结构a*和一个正确的偏移量。一种尝试是执行以下操作: int getint(struct A* base, size_t off) { return *(int*)((char*)base + off); } 这可能会起作用,但请注意,例
struct A {
int x;
};
size_t xoff = offsetof(A, x);
给定一个指向struct a
的指针,如何以标准一致性方式提取成员?当然,假设我们有一个正确的结构a*
和一个正确的偏移量。一种尝试是执行以下操作:
int getint(struct A* base, size_t off) {
return *(int*)((char*)base + off);
}
这可能会起作用,但请注意,例如,如果指针是同一数组的指针(或超过末尾的指针),则指针算术似乎只在标准中定义,而不必如此。从技术上讲,这个构造似乎依赖于未定义的行为
另一种方法是
int getint(struct A* base, size_t off) {
return *(int*)((uintptr_t)base + off);
}
这可能也会起作用,但请注意,intptr\u t
不一定存在,而且据我所知,intptr\u t
上的算术不一定产生正确的结果(例如,我记得一些CPU具有处理非字节对齐地址的能力,这意味着数组中每个字符的intptr\u t
以8步递增)
看起来标准中有些东西被遗忘了(或者我遗漏了)。根据7.19通用定义
,第3段,offsetof()
定义为:
宏是
NULL
扩展为实现定义的空指针常量;以及
offsetof(*type*, *member-designator*)
它扩展为具有类型的整型常量表达式
大小\u t
,其值是以字节为单位的偏移量
结构构件(由构件代号指定),从
其结构的开头(按类型指定)
因此,offsetoff()
返回以字节为单位的偏移量
和6.2.6.1概述,第4段规定:
存储在任何其他对象类型的非位字段对象中的值
包括
n×字符位位,其中n是该类型对象的大小,以字节为单位
由于CHAR\u BIT被定义为CHAR
中的位数,因此CHAR
是一个字节
因此,根据标准,这是正确的:
int getint(struct A* base, size_t off) {
return *(int*)((char*)base + off);
}
将base
转换为char*
并向地址添加off
字节。如果off
是offsetof(a,x);
的结果,则生成的地址是base
指向的结构a
中的x
的地址
你的第二个例子:
int getint(struct A* base, size_t off) {
return *(int*)((intptr_t)base + off);
}
取决于有符号的
intptr\t
值与无符号的size\t
值相加的结果。标准(6.5.6)的原因唯一允许对数组进行指针运算的是,结构可能有填充字节来满足对齐要求。所以在结构中进行指针运算实际上是形式上未定义的行为
实际上,只要您知道自己在做什么,它就可以工作。base+off
不能失败,因为我们知道那里有有效的数据,并且只要访问正确,数据不会错位
因此,(intptr\t)base+off
确实是更好的代码,因为不再有任何指针算法,只有普通的整数算法。因为intptr\t
是一个整数,所以它不是指针
正如在评论中指出的,该类型不保证存在,根据7.20.1.4/1,它是可选的。我想为了最大的可移植性,您可以切换到保证存在的其他类型,例如intmax\u t
或ptrdiff\u t
。但是,如果不支持intptr\u t
的C99/C11编译器是这并不都有用
(这里有一个小类型问题,即intptr\u t
是有符号类型,不一定与size\u t
兼容。您可能会遇到隐式类型升级问题。如果可能,使用uintpr\u t
更安全。)
接下来的问题是*(int*)((intptr_t)base+off)
是否是定义良好的行为。标准中关于指针转换的部分(6.3.2.3)规定:
任何指针类型都可以转换为整数类型。以下情况除外:
前面指定的结果是实现定义的。如果
无法在整数类型中表示结果,行为为
未定义。结果不必在任何
整数类型
对于这个特定的情况,我们知道这里有一个正确对齐的int
,所以这很好
(我也不认为存在任何指针别名问题。至少使用
gcc-O3-fstrict aliasing-Wstrict aliasing=2
编译不会破坏代码。)我非常确定将别名添加到char*
和指向同一对象的指针(不一定是数组)都是有效的。但仍在等待权威的答案。(char*)base
可用于在base
内的任何位置移动(并在末尾移动一个)。任何对象的行为都类似于大小为1的数组。返回*(int*)((char*)base+off)
很容易失败,因为int
访问可能未对齐。例如,int
访问可能会导致奇数地址上的总线故障。Oto OP说“假设……我们有一个正确的结构a*和一个正确的偏移量”,最好访问具有该字段类型或无符号字符的字段(无陷阱,无填充)。不清楚代码为什么不使用A->x
访问字段。如果A->x
没有提供,您该怎么做?如果所有代码都是A
,并且字段的偏移量x
,缺少字段类型/大小会阻止以一致的方式访问。引用的部分是非常不相关的f该标准将是6.5中关于指针别名的标准,或者可能是关于指针算术的部分。我不认为第二个示例会失败。intptr\u t
是无符号整数类型,而不是指针类型。它不会