tst*_*isl 14 c struct offsetof language-lawyer
我有以下代码:
#include <stddef.h>
int main() {
struct X {
int a;
int b;
} x = {0, 0};
void *ptr = (char*)&x + offsetof(struct X, b);
*(int*)ptr = 42;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
最后一行执行对 的间接访问x.b。
该代码是根据任何 C 标准定义的吗?
我知道:
*(char*)ptr = 42;已定义,但仅定义了实现。ptr == (void*)&x.b我猜想访问ptrvia指向的数据int*不会违反严格的别名规则,但我不完全确定该标准保证了这一点。
Nat*_*dge 14
是的,这是完全明确的定义,并且正是offsetof预期的使用方式。您对指向字符类型的指针进行指针算术,以便以字节为单位完成,然后转换回成员的实际类型。
例如,您可以看到 6.3.2.3 p7(所有引用均参考 C17 草案 N2176):
\n\n\n当指向对象的指针转换为指向字符类型的指针时,结果指向该对象的最低寻址字节。结果的连续增量,直到达到对象的大小,将产生指向对象的\n剩余字节的指针。
\n
因此(char *)&x,指针 tox转换为指针 to char,因此它指向 的最低寻址字节x。当我们相加offsetof(struct X, b)(假设是 4)时,我们就有了一个指向 的字节 4 的指针x。Nowoffsetof(struct X, b)定义为返回
\n\n结构成员从其结构开始的\n偏移量(以字节为单位)[7.19p3]
\n
所以 4 实际上是从xto开头的偏移量x.b。因此 的字节 4x是 的最低字节x.b,这就是ptr指向的;换句话说,ptr是一个指向 的指针x.b,但类型为char *。当我们将其转换回 时,我们得到了一个指向该类型的int *指针,该指针与我们从表达式中获得的类型完全相同。因此取消引用该指针会访问.x.bint *&x.bx.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前面的句子:
\n\n\n指向对象类型的指针可以转换为指向不同对象类型的指针。如果生成的\n指针未针对引用类型正确对齐,则行为未定义。否则,\n当再次转换回来时,结果应等于原始指针。
\n
这里没有对齐问题,因为char对齐要求最弱(6.2.8 p6)。因此,转换(char *)&x.b回int *必须恢复指向 的指针x.b,即(int *)(char *)&x.b == &x.b。
但ptr与 是相同的指针(char *)&x.b,因此我们可以用以下等式替换它们:(int *)ptr == &x.b。
显然*&x.b产生一个左值指定x.b(6.5.3.2 p4),因此 也是如此*(int *)ptr。
严格锯齿(6.5p7)没有问题。首先确定使用6.5p6的有效类型x.b:
\n\n访问其存储值的对象的有效类型是该对象的声明类型(if\nany)。[然后解释如果没有声明类型该怎么办。]
\n
嗯,x.b确实有一个声明的类型,即int. 所以它的有效类型是int.
现在查看严格别名下访问是否合法,请参见 6.5p7:
\n\n\n对象的存储值只能由具有以下类型之一的左值表达式访问:
\n\xe2\x80\x94 与对象的有效类型兼容的类型,
\n[更多选项与此处不相关]
\n
我们通过具有 type 的x.b左值表达式进行访问。并且与6.2.7p1兼容:*(int *)ptrintintint
\n\n如果两个类型的类型相同,则它们具有兼容类型。[然后是它们也可能兼容的其他条件]。
\n
这种技术的一个可能更熟悉的示例是按字节索引数组。如果我们有
\nint arr[100];\n*(int *)((char *)arr + (17 * sizeof(int))) = 42;\nRun Code Online (Sandbox Code Playgroud)\n那么这相当于arr[17] = 42;.
这就是像qsort和 一样的通用例程的bsearch实现方式。如果我们尝试使用 的qsort数组int,那么在qsort所有指针算术中,都是以字节为单位,在指向字符类型的指针上完成的,其中偏移量通过作为参数传递的对象大小(这里是sizeof(int))手动缩放。当qsort需要比较两个对象时,它将它们转换为const void *比较器函数并将它们作为参数传递给比较器函数,比较器函数将它们转换回const int *进行比较。
这一切都运行良好,并且显然是该语言的预期功能。offsetof因此,我认为我们不必怀疑当前问题中的使用同样是一个预期的功能。