此代码段打印该值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;
}
Run Code Online (Sandbox Code Playgroud)
当我创建时b
,指向它的指针将指向数据{ {5}, 10 }
.
当我&b
转向时struct A*
,我向编译器保证这struct A*
指向数据类型的单个数据元素的结构int
.相反,我提供一个指针数据类型的两个数据元素的结构struct A
,int
.
即使第二个变量被忽略(因为struct A
只有一个数据成员),我仍然提供一个结构,其成员是数据类型struct A
,而不是int
.
因此,当我通过在a
给printA
,线a->x
进行的,基本上,要求访问的第一个数据元素a
.第一个数据元素a
是数据类型struct A
,由于%d
期望数字而不是a ,这是一种类型不匹配struct A
.
到底发生了什么?
当我创建时
b
,指向它的指针将指向数据{ {5}, 10 }
.
是的,从某种意义上讲,它是适合类型且价值正确的C初始化程序的文本.这文字本身不应该被字面上的结构的价值.
当我
&b
转向时struct A*
,我向编译器保证这struct A*
指向数据类型为int的单个数据元素的结构.
不,不完全是.您正在将表达式的值转换&b
为type struct A *
.结果指针实际指向a struct A
是一个单独的问题.
相反,我提供一个指针数据类型的两个数据元素的结构
struct A
,int
.
不,不是"反而".鉴于struct B
第一个成员是a struct A
,并且C禁止在结构的第一个成员之前填充,指向a的指针struct B
也指向a struct A
- B的第一个成员 - 在一般意义上.正如@EricPostpischi在注释中观察到的那样,C标准在您的特定情况下明确指定结果:给定struct B b
,将指针转换b
为类型会struct A *
产生指向b
第一个成员的指针struct A
.
即使第二个变量被忽略(因为
struct A
只有一个数据成员),我仍然提供一个结构,其成员是数据类型struct A
,而不是int
.
表示其第一个成员的表示形式的第一个sizeof(struct A)
字节struct B
,a struct A
.后者是前者的成员除了在记忆中的重叠之外没有任何物理表现.
即使语言没有明确指定它,给定你的变量声明b
为a struct B
,也没有实际的理由期望表达式(struct A*)&b == &b.a
会被评估为false,并且毫无疑问右手指针可以用于访问a struct A
.
因此,当我传入a时
printA
,a->x
执行该行,基本上要求访问第一个数据元素a
.
是的,这就是断言进入的地方a
确实指向a struct A
.正如您已经讨论过的,它在您的情况下做了哪些.
第一个数据元素
a
是数据类型struct A
,
*a
根据定义,编号 是struct A
.具体来说,它struct A
的表示与表示的开头重叠b
.如果不存在这样struct A
的行为,那么行为将是未定义的,但这不是问题.像每一个struct A
,它有一个成员,由x
,指定,是int
.
由于
%d
期望数字而不是a,这是类型不匹配struct A
.
你的意思是期待一个int
.这就是它的结果.这就是表达式所a->x
读取的内容,假设行为已完全定义,因为这是该表达式的类型.在不同的情况下,行为可能确实没有定义,但在任何情况下,该表达都不会提供struct A
.
到底发生了什么?
似乎正在发生的事情是你正在想象比C实际提供的更高级别的语义.特别是,您似乎将结构的心智模型作为可区分成员对象的列表,这会导致您形成不正确的期望.
也许您更熟悉一种弱类型语言,如Perl,或动态类型语言,如Python,但C的工作方式不同.您无法查看C对象并且有用地询问"您的类型是什么"?相反,您通过用于访问它的表达式的静态类型的镜头来查看每个对象.
语言 - 律师解释为什么代码是好的:
作为一种特殊情况,指向结构类型的指针等效于指向其第一个成员的指针.C176.7.2§15的相关部分说:
指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然.
这意味着没问题(struct A*)&b
.&b
适当地转换为正确的类型.
没有违反"严格别名",因为我们符合C176.5§7:
对象的存储值只能由具有以下类型之一的左值表达式访问:
- 与对象的有效类型兼容的类型,...
- 聚合或联合类型,包括其成员中的上述类型之一
初始成员的有效类型struct A
.在print函数中发生的左值访问很好.struct B
也是一种包含struct A
其成员的聚合类型,因此无论在顶部引用的初始成员规则如何,都不可能发生严格的别名违规.