10

假设我有一个结构并将偏移量提取到一个成员:

struct A {
    int x;
};

size_t xoff = offsetof(A, x);

struct A给定一个指针以符合标准的方式提取成员,我该如何做?当然假设我们有一个正确struct 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_t它不需要存在,据我所知,算法intptr_t不需要产生正确的结果(例如,我记得某些 CPU 有能力处理非字节对齐的地址,这将建议intptr_t以 8 的步长为每个char数组中的每个增加)。

看起来标准中忘记了一些东西(或者我错过了一些东西)。

4

2 回答 2

3

根据C 标准7.19 通用定义<stddef.h>,第 3 段,offsetof()定义为:

宏是

NULL

它扩展为实现定义的空指针常量;和

offsetof(*type*, *member-designator*)

它扩展为具有 type 的整数常量表达式 size_t,其是以字节为单位的偏移量,从其结构的开头(由type指定)到结构成员(由member-designator 指定)。

因此,返回以字节offsetoff()为单位的偏移量。

并且6.2.6.1 总则第 4 段规定:

存储在任何其他对象类型的非位域对象中的值由 n × CHAR_BIT位组成,其中n是该类型对象的大小,以字节为单位。

由于CHAR_BIT定义为 a 中的位数char,因此 achar是一个字节

因此,根据标准,这是正确的:

int getint(struct A* base, size_t off) {
    return *(int*)((char*)base + off); 
}

这将转换base为 achar *并将off字节添加到地址中。如果off是 的结果offsetof(A, x);,则结果地址是 指向的内的x地址。structure Abase

你的第二个例子:

int getint(struct A* base, size_t off) {
    return *(int*)((intptr_t)base + off);
}

取决于有符号intptr_t值与无符号值相加的结果size_t

于 2016-05-24T12:46:08.770 回答
0

标准(6.5.6)仅允许对数组进行指针运算的原因是结构可能具有填充字节以满足对齐要求。因此,在结构中进行指针运算确实是形式上未定义的行为。

在实践中,只要您知道自己在做什么,它就会起作用。base + off不会失败,因为我们知道那里有有效的数据,而且它没有错位,只要它被正确访问。

因此(intptr_t)base + off确实是更好的代码,因为不再有任何指针运算,而只是简单的整数运算。因为intptr_t是整数,所以不是指针。

正如评论中所指出的,这种类型不保证存在,根据 7.20.1.4/1,它是可选的。我想为了获得最大的可移植性,您可以切换到保证存在的其他类型,例如intmax_tor ptrdiff_t。然而,如果没有支持的 C99/C11 编译器是否intptr_t有用,则值得商榷。

(这里有一个小类型问题,即它intptr_t是有符号类型,不一定与 兼容size_t。您可能会遇到隐式类型提升问题。uintptr_t如果可能,使用它更安全。)

下一个问题是是否*(int*)((intptr_t)base + off)是明确定义的行为。关于指针转换的标准部分(6.3.2.3)说:

任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

对于这种特定情况,我们知道我们在那里有一个正确对齐的int,所以没关系。

(我也不相信任何指针别名问题都适用。至少编译gcc -O3 -fstrict-aliasing -Wstrict-aliasing=2不会破坏代码。)

于 2016-05-24T13:49:55.293 回答