问题描述
在此讨论中,我假定标量对象是整数,浮点数,字符,布尔值和指针之类的东西。非标量对象是由标量类型和递归聚合组成的聚合(结构)。
基于这种假设,C ++程序是否曾经从标量组件中访问聚合?
例如:
struct s { int a; float b; };
void assign1(s& out,s const& in) { out = in; }
void assign2(s& out,s const& in) { out.a = in.a; out.b = in.b; }
实际上,assign1
和assign2
等效,并且都访问int s::a
和float s::b
。但是它们中的任何一个也是否在任何意义上都访问了整个聚合?
仅实际访问标量对象的解释会产生有趣的结果。
例如,根据我的另一个问题here的解决方案,形成对对象的引用并不构成访问。在这种分辨率下,我可以编写如下函数:
void assign3(s& out,s const& in) {
int& a_out = out.a; // no access
int const& a_in = in.a; // no access
a_out = a_in; // access some ints
}
除第三行外,没有“访问”发生,该行访问某些int。 out
和in
是否实际上引用了s
类型的对象是无关紧要的。实际上,只有a_out
和a_in
必须引用整数。
鉴于此,而且对象的地址就是其第一个非静态数据成员的地址,我有写权限。
int out,const in = 42;
assign3(reinterpret_cast<s&>(out),reinterpret_cast<s const&>(in));
如果所有这些假设都成立,那么C和C ++在大多数情况下只是可移植的汇编语言,而别名规则只是帮助编译器正确地从x87浮点协处理器中读取寄存器。
当然,这些假设不成立。我错了。但是为什么我错了吗?为什么标准文档对有效类型或动态类型具有所有这些规则?
给定struct a { int a; }; struct b { int b; };
,使a::a
通过b::b
进入{{1}}具有不确定性,除了在某些涉及工会的有限情况下有什么好处?
解决方法
对编译器重要的事情之一是从内存中重新加载寄存器。这需要时间,最好避免。因此,如果您知道在地址p
处有一个Foo
的{{1}}结构,那么您就有一个寄存器中的第一个浮点数,然后又写入了地址{{ 1}},您是否需要重新加载第一个浮点数?如果您无法证明float[2]
,则可能有必要。但是,如果您知道地址q
上的浮点数是p!=q
的一部分,因此后面跟着q
,则可以证明Bar
。因此,写入int
不会强制从p!=q
重新加载。
请注意,q
和p
之后的数据不在此处读写。只是事实是这些类型不同,这使编译器可以优化p
的冗余读取。