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
是无符号整数类型,而不是指针类型。它不会