假设我有一些结构定义如下:
struct foo { int a; };
struct bar { struct foo r; int b; };
struct baz { struct bar z; int c; };
C标准是否保证以下代码严格符合?
struct baz x;
struct foo *p = (void *)&x;
assert(p == &x.z.r);
这个结构的动机是提供一致的编程习惯用法,用于转换为已知兼容的指针类型。
现在,这就是C关于结构及其初始成员如何可转换的说法:
在结构对象中,非位字段成员和位字段所在的单元具有按声明顺序增加的地址。指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。结构对象中可能有未命名的填充,但不是在其开头 C.11§6.7.2.1¶15
这就是关于void
指针转换的说法:
指向
void
的指针可以转换为指向任何对象类型的指针。指向任何对象类型的指针可以转换为指向void
的指针,然后再返回;结果应该等于原始指针 C.11§6.3.2.3¶1
这就是关于在对象指针类型之间进行转换的说法:
指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐引用类型 68),则行为未定义。否则,当再次转换回来时,结果应该等于原始指针。
68)一般来说,“正确对齐”的概念是可传递的:如果指向类型A的指针正确对齐 指向类型B的指针,它又为指向C类型的指针正确对齐,然后指向类型A的指针正确对齐指向类型C的指针。
C.11§6.3.2.3¶7
我从上面的理解是,通过void *
转换将对象指针转换为不同类型的对象指针是完全正确的。但是,我得到的评论表明不是这样。
答案 0 :(得分:4)
你的榜样严格遵守。
§6.7.2.1¶15中的2 nd 句子(指向a的指针 结构对象,适当转换,指向其初始成员......反之亦然。)保证以下等式:
(sruct bar *) &x == &(x.z)
(struct foo *) &(x.z) == &(x.z.r)
当你在struct
的开头时,不会出现填充,我对标准的理解是struct
及其第一个元素的地址是相同的。
所以struct foo *p = (void *) &x;
是正确的struct foo *p = (struct foo *) &x;
在该特定情况下,根据§6.7.2.1¶15保证对齐是正确的。它始终允许通过void *
传递,但没有必要,因为§6.3.2.3¶7允许指向不同对象的指针之间的转换,只要没有对齐问题
应该注意§6.2.3.2¶7也说:当指向对象的指针转换为指向字符类型的指针时,
结果指向对象的最低寻址字节,这意味着所有这些指针实际上指向x.r.z.a
的最低寻址字节。所以你也可以通过指针传递给char
,因为我们也有:
(char *) &x == (char *) &(x.z) == (char *) &(x.z.r) == (char *) &(x.z.r.a)
答案 1 :(得分:4)
要完成分析,有必要查看定义指针相等性的定义:
...如果一个操作数是指向对象类型的指针而另一个是指向
void
的限定或非限定版本的指针,则前者转换为后者。两个指针比较相等,当且仅当两个都是空指针时,两者都是指向同一对象的指针(包括指向对象的指针和在其开头的子对象)或函数,两者都指向超过最后一个元素的指针。相同的数组对象,或者一个是指向一个数组对象末尾的指针,另一个是指向不同数组对象的开头的指针,该数组对象恰好跟随地址空间中的第一个数组对象。
C.11§6.5.9¶5-6
所以,这是一个定义明确的论点:
(void *)&x == (void *)&x.z
§6.7.2.1¶15,§6.5.9¶5-6
(void *)&x.z == &x.z.r
§6.7.2.1¶15,§6.5.9¶5-6
(void *)&x == &x.z.r
传递平等
(struct foo *)(void *)&x == (void *)&x
§6.3.2.3¶1,§6.5.9¶5-6
(struct foo *)(void *)&x == &x.z.r
传递平等
上面的最后一步是初始化p
的本质和问题中代码的断言。
答案 2 :(得分:1)
恕我直言,除了对标准的严格解释会产生疑问之外,内存中对象的分配在同一编译器上遵循相同的规则:提供适合任何类型变量的地址。 因为每个结构都以其传递属性的第一个变量开头,所以结构本身将与适合任何变量的地址对齐。后者使人怀疑不同结构具有不同的地址,转换之间无法进行修改,因为地址定义遵循相同的规则。这当然不适用于以下结构字段,这些结构字段可能不符合以下字段的对齐要求。 如果您对结构的第一个元素进行操作,则保证与结构本身的第一个字段相同。
现在看一下最常见的软件之一:the Independent JPEG group JPEGlib。 在许多处理器和机器上编译的整个软件使用的技术类似于C ++管理传递结构,它始终是相同的,但在调用之间存在许多其他的,不同的子结构和字段。
此代码可编译并运行从玩具到PC到平板电脑等任何东西......
答案 3 :(得分:1)
是的,就语言标准而言,您的示例严格遵守,因此完全合法。这主要来自您提供的2个引号(重要的是突出显示)。第一个:
指向void的指针可以转换为指向任何对象类型的指针。
这意味着在代码中的赋值中,我们有一个从struct baz指向void指针的成功转换,然后,由于两个指针都是相等对齐的事实,成功地从void指针转换为struct。如果情况并非如此,由于您不遵守您提供的6.3.2.3,我们会有未定义的行为。
第二个:
68)一般来说,“正确对齐”这个概念是传递性的:如果指向类型A的指针正确地对齐指向类型B的指针,而指针又针对指向类型C的指针正确对齐,那么对于指向类型C的指针,指向类型A的指针正确对齐。
这一点更重要。它没有声明(也不应该)类型A和C必须是相同的,反过来,它们不允许它们。唯一的限制是alingment。
那就是它。
然而,当然,由于显而易见的原因,这种操纵是不安全的。