是的,这是非常明确的定义,并且正是offsetof
预期的使用方式。您对指向字符类型的指针执行指针运算,以便以字节为单位完成,然后转换回成员的实际类型。
例如,您可以看到 6.3.2.3 p7(所有引用均指向 C17 草案 N2176):
当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续增量,直到对象的大小,产生指向对象剩余字节的指针。
(char *)&x
一个指针x
转换为指向 的指针也是如此char
,因此它指向 的最低寻址字节x
。当我们添加offsetof(struct X, b)
(比如说它是 4)时,我们有一个指向字节 4 的指针x
。现在offsetof(struct X, b)
定义为返回
从结构的开头到结构成员的字节偏移量 [7.19p3]
所以 4 实际上是从 to 开始的偏移x
量x.b
。因此字节 4 ofx
是 的最低字节x.b
,这就是ptr
指向;换句话说,ptr
是指向 的指针x.b
,但类型为char *
。当我们将它转换回时int *
,我们有一个指向x.b
which 的类型的指针int *
,与我们从表达式中得到的完全相同&x.b
。所以取消引用这个指针访问x.b
.
关于最后一步的评论中出现了一个问题:什么时候ptr
被强制转换回int *
,我们怎么知道我们确实有一个指向 的指针int
x.b
?这在标准中不太明确,但我认为这是明显的意图。
但是,我认为我们也可以间接推导出它。希望我们同意ptr
上面是一个指向x.b
. 现在通过上面引用的 6.3.2.3 p7 的同一段落,获取一个指针x.b
并将其转换为char *
,如在 中(char *)&x.b
,也将产生一个指向 的最低寻址字节的指针x.b
。因为它们是指向相同字节的相同类型的指针,所以它们是相同的指针:ptr == (char *)&x.b
.
然后我们看6.3.2.3 p7前面几句:
指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐引用的类型,则行为未定义。否则,当再次转换回来时,结果将等于原始指针。
这里对齐没有问题,因为char
对齐要求最弱(6.2.8 p6)。所以转换(char *)&x.b
回int *
必须恢复指向 的指针x.b
,即(int *)(char *)&x.b == &x.b
。
Butptr
和 是同一个指针(char *)&x.b
,所以我们可以用这个等式替换它们:(int *)ptr == &x.b
.
显然*&x.b
会产生一个左值指定x.b
(6.5.3.2 p4),因此*(int *)ptr
.
严格别名(6.5p7)没有问题。首先,确定x.b
使用6.5p6的有效类型:
访问其存储值的对象的有效类型是对象的声明类型(如果有)。[然后解释如果它没有声明的类型该怎么办。]
好吧,x.b
确实有一个声明的类型,即int
. 所以它的有效类型是int
。
现在看看在严格别名下访问是否合法,参见 6.5p7:
对象的存储值只能由具有以下类型之一的左值表达式访问:
— 与对象的有效类型兼容的类型,
[更多选项与此处无关]
我们通过具有类型x.b
的左值表达式进行访问。并且与每 6.2.7p1 兼容:*(int *)ptr
int
int
int
如果它们的类型相同,则两种类型具有兼容的类型。[然后它们也可能兼容的其他条件]。
可能更熟悉的相同技术的一个示例是按字节索引到数组中。如果我们有
int arr[100];
*(int *)((char *)arr + (17 * sizeof(int))) = 42;
那么这相当于arr[17] = 42;
.
这就是通用例程喜欢qsort
和bsearch
实现的方式。如果我们尝试使用qsort
的数组int
,那么在qsort
所有指针运算中都以字节为单位,在指向字符类型的指针上完成,偏移量由作为参数传递的对象大小手动缩放(此处为sizeof(int)
)。当qsort
需要比较两个对象时,它将它们转换为const void *
并将它们作为参数传递给比较器函数,比较器函数将它们转换回以const int *
进行比较。
这一切都很好,显然是该语言的预期功能。所以我认为我们不必怀疑offsetof
在当前问题中使用的同样是一个预期的功能。