【问题标题】:Cast struct pointer to another struct将结构指针转换为另一个结构
【发布时间】:2023-03-16 00:26:01
【问题描述】:

此代码 sn-p 打印值 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(void)
{
    struct B b = {
        {
            5
        },
        10
    };
    
    struct A *a = (struct A*)&b;
    printA(a);
    
    printf("Done.\n");
    return 0;
}

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

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

即使第二个变量被忽略(因为struct A 只有一个数据成员),我仍然为其提供一个结构,其成员的数据类型为struct A,而不是int

因此,当我将a 传递给printA 时,会执行a-&gt;x 行,本质上是要求访问a 的第一个数据元素。 a 的第一个数据元素是数据类型 struct A,这是由于 %d 需要一个数字而不是 struct A 而导致的类型不匹配。

这里到底发生了什么?

【问题讨论】:

  • struct A 只是内存中的一个 int。 struct B 只有 2 个整数(以及更复杂结构的任何填充)。你也可以printf("%d", (int*) b) 看到 5。

标签: c struct casting


【解决方案1】:

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

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

当我将 &amp;b 转换为 struct A* 时,我向编译器保证这 struct A* 指向数据类型的单个数据元素的结构 诠释。

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

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

不,不是“代替”。鉴于struct B 的第一个成员是struct A,并且C 禁止在结构的第一个成员之前进行填充,指向struct B 的指针 指向struct A - - B 的第一个成员——在一般意义上。正如@EricPostpischi 在 cmets 中观察到的那样,C 标准明确指定了特定情况下的结果:给定 struct B b,将指向 b 的指针转换为类型 struct A * 会产生指向 b 的第一个成员的指针。 struct A

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

struct B 表示的第一个 sizeof(struct A) 字节构成其第一个成员 struct A 的表示。后者是前者的一员,除了它们在记忆中的重叠外,没有任何物理表现。

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

因此,当我将 a 传递给 printA 时,会执行 a-&gt;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-&gt;x 读取的内容,假设行为已被定义,因为那是该表达式的类型。在不同的情况下,行为可能确实没有定义,但在任何情况下,该表达式都不会提供struct A

这里到底发生了什么?

似乎正在发生的事情是,您正在想象与 C 实际提供的不同的、更高级别的语义。特别是,您似乎将结构的心理模型视为可区分成员对象的列表,这导致您形成错误的期望。

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

【讨论】:

  • C 2011 6.7.2.1 15 表示指向经过适当转换的结构的指针(例如,指向指向其第一个成员类型的指针的类型)会产生指向其第一个成员的指针。
  • 谢谢,@EricPostpischil。我已经更新了答案以反映您的观察,这确实导致了更强大的立场。
【解决方案2】:

为什么代码没问题的语言律师解释:

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

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

  • 这意味着(struct A*)&amp;b 没问题。 &amp;b 被适当地转换为正确的类型。

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

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

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

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

【讨论】:

    【解决方案3】:

    C 标准中有一条针对这种情况的特殊规则。 C 2011 6.7.2.1 15 说:

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

    【讨论】:

      猜你喜欢
      • 2011-04-15
      • 2011-04-08
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-31
      • 2015-01-24
      相关资源
      最近更新 更多