代码之家  ›  专栏  ›  技术社区  ›  Hatefiend

转换指向其他结构的结构指针

  •  2
  • Hatefiend  · 技术社区  · 6 年前

    此代码段打印值 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* ,我向编译器保证 结构A* 指向数据类型的单个数据元素的结构 int . 相反,我为它提供了一个指向由两个数据类型的数据元素组成的结构的指针 struct A , 内景 .

    即使第二个变量被忽略(因为 结构A 只有一个数据成员)我仍在为它提供一个成员为数据类型的结构 结构A ,不是 内景 .

    所以,当我进去的时候 a printA ,行 a->x 执行,基本上要求访问 . 的第一个数据元素 属于数据类型 结构A ,这是由于 %d 需要一个数字,而不是 结构A .

    这里到底发生了什么?

    3 回复  |  直到 6 年前
        1
  •  6
  •   John Bollinger    6 年前

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

    是的,从某种意义上说,它是一个类型适当且值正确的C初始值设定项的文本。文本本身不应该被采纳 字面意思 作为结构的价值。

    当我投的时候 &b struct A* ,我向编译器保证 结构A* 指向数据类型的单个数据元素的结构 内景。

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

    相反,我提供了一个指向两个数据的结构的指针 数据类型元素 结构A , int .

    不,不是“相反”。既然如此 struct B 的第一个成员是 结构A ,并且C禁止在结构的第一个成员(指向 结构B 指向 结构A --在一般意义上,B是第一个成员。正如@ericppostischi在注释中所观察到的,C标准显式地指定了特定情况下的结果:给定 struct B b ,将指针转换为 键入 结构A* 生成指向 的第一个成员 结构A .

    即使第二个变量被忽略(因为 结构A 只有一个 数据成员)我仍在为它提供一个成员为数据的结构 类型 结构A ,不是 内景 .

    第一次 sizeof(struct A) 表示的字节数 结构B 组成其第一个成员的代表,a 结构A . 后者是前者的一员,除了记忆上的重叠外,没有其他身体表现。

    即使语言没有显式地指定它,给定变量的声明 作为一个 结构B ,没有实际的理由期望 (struct A*)&b == &b.a 将计算为false,并且毫无疑问,可以使用右手指针访问 结构A .

    因此,当我把一个 printA ,行 a->x 被执行, 本质上要求访问 a .

    是的,这就是断言输入的地方 确实是指 结构A . 正如我们已经讨论过的,在你的情况下是这样的。

    第一次 数据元素 属于数据类型 结构A ,

    不。 *a 根据定义是 结构A . 具体来说,就是 结构A 其表示与 . 如果没有这样一个 结构A 那么行为将是未定义的,但这不是一个问题。就像每一个 结构A ,它有一个成员,由 x ,这是一个 内景 .

    类型不匹配 由于 %d 需要一个数字,而不是 结构A .

    你是说期待 内景 . 这就是它得到的。这就是表达 a->x 读取,假设行为已定义,因为这是该表达式的类型。在不同的情况下,行为可能确实没有定义,但在任何情况下,该表达式都不能提供 结构A .

    这里到底发生了什么?

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

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

        2
  •  6
  •   Lundin Makoto    6 年前

    语言律师解释为什么该守则是罚款的:

    • C中的任何指针都可以转换为任何其他指针类型。(C17 6.3.2第7条)。
    • 如果在转换后安全地取消引用指向对象取决于:1)类型是否兼容,从而正确对齐;2)是否允许使用的相应指针类型进行别名。
    • 作为特殊情况,指向结构类型的指针相当于指向其第一个成员的指针。C17 6.7.2第15条的相关部分说:

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

    • 这意味着 (struct A*)&b 很好。 &b 已适当转换为正确的类型。

    • 不违反“严格别名”,因为我们符合C17 6.5第7条:

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

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

      初始成员的有效类型为 struct A . 在print函数中发生的左值访问是可以的。 struct B 也是一种聚合类型,包括 结构A 在它的成员中,这样严格的别名违规是不可能的,不管最上面引用的初始成员规则是什么。

        3
  •  3
  •   Eric Postpischil    6 年前

    对于这种情况,C标准中有一个特殊的规则。C 2011 6.7.2.1 15表示:

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