此代码段显示值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 A
,int
的两个数据元素的结构。
即使忽略第二个变量(由于struct A
仅具有一个数据成员),我仍将为其提供一个结构,其成员的数据类型为struct A
,而不是int
。 / p>
因此,当我将a
传递到printA
时,将执行行a->x
,实际上是要求访问a
的第一个数据元素。 a
的第一个数据元素的数据类型为struct A
,由于%d
期望的是数字而不是struct A
,因此数据类型不匹配。
这里到底发生了什么?
答案 0 :(得分:6)
当我创建
b
时,指向它的指针将指向数据{ {5}, 10 }
。
是的,就某种意义上来说,它是类型适当且值正确的C初始化程序的文本。该文本本身不应该从字面上看作为结构的值。
当我将
&b
强制转换为struct A*
时,我向编译器保证struct A*
指向数据类型为单个数据元素的结构
不,不完全是。您正在转换表达式&b
的值以键入struct A *
。结果指针是否实际指向struct A
是一个单独的问题。
相反,我为其提供了指向两个数据结构的指针 数据类型
struct A
,int
的元素。
不,不是“相反”。假设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)
有关代码为何正确的语言律师解释:
在特殊情况下,指向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说:
指向经过适当转换的结构对象的指针指向其初始成员(或者,如果该成员是位字段,则指向它所驻留的单元),反之亦然。