通过一个很好定义的void指针进行指针转换?

时间:2015-07-15 01:46:57

标签: c pointers language-lawyer

假设我有一些结构定义如下:

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 *转换将对象指针转换为不同类型的对象指针是完全正确的。但是,我得到的评论表明不是这样。

4 个答案:

答案 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。

那就是它。

然而,当然,由于显而易见的原因,这种操纵是不安全的。