将结构指针转换为另一个结构

时间:2018-08-08 23:50:54

标签: c struct casting

此代码段显示值5。我不明白为什么。

#include <stdio.h>

struct A
{
    int x;
};

struct B
{
    struct A a;
    int y;
};

void printA(struct A *a)
{
    printf("A obj: %d\n", a->x);
}

int main()
{
    struct B b = {
        {
            5
        },
        10
    };

    struct A *a = (struct A*)&b;
    printA(a);

    printf("Done.\n");

    return 0;
}

当我创建b时,指向它的指针将指向数据{ {5}, 10 }

当我将&b强制转换为struct A*时,我向编译器保证,该struct A*指向数据类型为int的单个数据元素的结构。相反,我为它提供了一个指针,该指针指向数据类型为struct Aint的两个数据元素的结构。

即使忽略第二个变量(由于struct A仅具有一个数据成员),我仍将为其提供一个结构,其成员的数据类型为struct A,而不是int。 / p>

因此,当我将a传递到printA时,将执行行a->x,实际上是要求访问a的第一个数据元素。 a的第一个数据元素的数据类型为struct A,由于%d期望的是数字而不是struct A,因此数据类型不匹配。

这里到底发生了什么?

3 个答案:

答案 0 :(得分:6)

  

当我创建b时,指向它的指针将指向数据{ {5}, 10 }

是的,就某种意义上来说,它是类型适当且值正确的C初始化程序的文本。该文本本身不应该从字面上看作为结构的值。

  

当我将&b强制转换为struct A*时,我向编译器保证   struct A*指向数据类型为单个数据元素的结构   

不,不完全是。您正在转换表达式&b的值以键入struct A *。结果指针是否实际指向struct A是一个单独的问题。

  

相反,我为其提供了指向两个数据结构的指针   数据类型struct Aint的元素。

不,不是“相反”。假设struct B的第一个成员是struct A,并且C禁止在结构的第一个成员之前进行填充,则指向struct B的指针也指向 一般而言,是struct A(B的第一个成员)。正如@EricPostpischi在注释中观察到的那样,C标准明确指定了您的特定情况下的结果:给定struct B b,将指向b的指针转换为类型struct A *会生成指向{{1}的指针}的第一位成员。b

  

即使忽略了第二个变量(因为struct A只有一个   数据成员)我仍在为其提供成员数据结构   键入struct A,而不是struct A

int表示形式的前sizeof(struct A)个字节构成其第一个成员struct B的表示形式。后者是前者的成员,除了记忆重叠外,没有其他身体表现。

即使该语言没有明确指定它,鉴于您将变量struct A声明为b,也没有实际理由期望表达式struct B的计算结果为否,那么毫无疑问可以使用右侧指针访问(struct A*)&b == &b.a

  

因此,当我向struct A传递a时,将执行行printA,   本质上要求访问a->x的第一个数据元素。

是的,这是断言输入a确实指向a的地方。正如您所讨论的,它针对您的情况进行处理。

  

第一个   struct A的数据元素的数据类型为a

不。根据定义,struct A*a。具体来说,是struct A的表示与struct A的表示的开头重叠。如果没有这样的b,则行为将是不确定的,但这不是问题。像每个struct A一样,它具有一个由struct A指定的成员,即x

  

这是类型不匹配   由于int期望输入数字,而不是%d

您的意思是期望一个struct A。就是这样。假设行为已定义,这就是表达式int读取的内容,因为这是该表达式的类型。在不同情况下,可能确实无法定义行为,但是在任何情况下,该表达式都不会提供a->x

  

这里到底发生了什么?

似乎正在发生的事情是,您正在想象与C实际提供的不同的更高级别的语义。尤其是,您似乎拥有一个作为可区分成员对象列表的结构心理模型,这导致您形成不正确的期望。

也许您更熟悉弱类型语言(例如Perl)或动态类型语言(例如Python),但是C的工作方式有所不同。您不能看C对象,而有用地问“您的类型是什么”?相反,您将通过用于访问它的表达式的静态类型的镜头来查看每个对象。

答案 1 :(得分:6)

有关代码为何正确的语言律师解释:

  • C中的任何指针都可以转换为任何其他指针类型。 (C17 6.3.2§7)。
  • 在转换之后是否安全地取消对指向对象的引用取决于:1)类型是否兼容并因此正确对齐,以及2)是否允许使用的各个指针类型进行别名。
  • 在特殊情况下,指向struct类型的指针等效于指向其第一个成员的指针。 C17 6.7.2§15的相关部分说:

      

    指向结构对象的指针,   经过适当转换后,指向其初始成员(或者,如果该成员是位字段,则指向   它所在的位置),反之亦然。

  • 这表示(struct A*)&b很好。 &b已适当地转换为正确的类型。

  • 因为我们满足C17 6.5§7的要求,所以没有违反“严格别名”:

      

    只能通过具有以下类型之一的左值表达式访问对象的存储值:

         
        
    • 与对象的有效类型兼容的类型,   ...
    •   
    • 在成员中包括上述类型之一的聚合或联合类型
    •   

    初始成员的有效类型为struct A。在print函数内部进行的左值访问很好。 struct B也是一个聚合类型,在其成员中包括struct A,因此,无论顶部引用的初始成员规则如何,都不可能严格违反别名。

答案 2 :(得分:3)

在这种情况下,C标准中有一条特殊规则。 C 2011 6.7.2.1 15说:

  

指向经过适当转换的结构对象的指针指向其初始成员(或者,如果该成员是位字段,则指向它所驻留的单元),反之亦然。

相关问题