[英]Array of non-contiguous objects
#include <iostream>
#include <cstring>
// This struct is not guaranteed to occupy contiguous storage
// in the sense of the C++ Object model (§1.8.5):
struct separated {
int i;
separated(int a, int b){i=a; i2=b;}
~separated(){i=i2=-1;} // nontrivial destructor --> not trivially copyable
private: int i2; // different access control --> not standard layout
};
int main() {
static_assert(not std::is_standard_layout<separated>::value,"sl");
static_assert(not std::is_trivial<separated>::value,"tr");
separated a[2]={{1,2},{3,4}};
std::memset(&a[0],0,sizeof(a[0]));
std::cout<<a[1].i;
// No guarantee that the previous line outputs 3.
}
// compiled with Debian clang version 3.5.0-10, C++14-standard
// (outputs 3)
削弱标准保证以至该程序可能显示不确定行为的背后的原理是什么?
该标准说:“数组类型的对象包含T类型的N个子对象的连续分配的非空集。” [dcl.array]§8.3.4。 如果类型T的对象不占用连续存储空间,那么此类对象的数组怎么办?
编辑:删除可能分散注意力的解释性文字
1.这是实际编写编译器的公司所采用的Occam剃刀实例:不要提供超出解决问题所需的更多保证,因为否则,您的工作量将增加一倍而没有任何补偿。 问题的一部分是适合于高级硬件或历史性硬件的复杂类。 (由BaummitAugen和MM提示)
2.(连续=共享公共边界,相邻或依次共享)
首先,不是T类型的对象总是或从不占用连续存储。 单个二进制文件中的同一类型可能存在不同的内存布局。
[class.derived]§10(8):基类子对象的布局可能不同于...
这足以使您退缩并满意我们的计算机上发生的事情与标准不矛盾。 但是,让我们修改问题。 更好的问题是:
标准是否允许不单独占用连续存储的对象数组,而同时每两个连续的子对象共享一个公共边界?
如果是这样,这将严重影响char *算术与T *算术的关系。
根据您是否理解OP标准报价(意味着只有子对象共享一个公共边界,或者在每个子对象内,这些字节也共享一个公共边界),您可能得出不同的结论。
假设第一个,您会发现“连续分配”或“连续存储”可能仅表示&a [n] ==&a [0] + n(第23.3.2.1节),这是有关子对象地址的陈述,并不意味着该数组位于单个连续字节序列内。
如果您假设使用更强的版本,则可能会得出T *与char *指针算术得出的'element offset == sizeof(T)'结论。这也暗示着一个人可能将否则可能不连续的对象强制为连续的对象通过声明T t [1]进行布局; 代替T t;
现在如何解决这个混乱局面? 标准中对sizeof()运算符有一个根本上模棱两可的定义,这似乎是至少在每个体系结构上键入大致相等的布局的时间的遗留物,而不再是这种情况。 ( 新的展示位置如何知道要创建哪个布局? )
当应用于类时,结果[ofofof()]是该类的对象中的字节数,包括将该类型的对象放置在数组中所需的任何填充。 [expr.sizeof]§5.3.3(2)
但是,等等,所需的填充量取决于布局,并且单个类型可能具有多个布局。 因此,我们势必会加一粒盐,并在所有可能的布局中采用最小的盐,或者做同样任意的事情。
最后,如果这是预期的含义,则数组定义将从char *算术方面的歧义中受益。 否则,对问题1的答案将适用。
与现在删除的答案和注释相关的一些评论:如技术参考中所述,对象在技术上可以占据非连续的存储字节吗? ,实际上存在不连续的对象。 此外,天真地设置子对象可能会使包含对象的不相关子对象无效,即使对于完全连续且可复制的对象也是如此:
#include <iostream>
#include <cstring>
struct A {
private: int a;
public: short i;
};
struct B : A {
short i;
};
int main()
{
static_assert(std::is_trivial<A>::value , "A not trivial.");
static_assert(not std::is_standard_layout<A>::value , "sl.");
static_assert(std::is_trivial<B>::value , "B not trivial.");
B object;
object.i=1;
std::cout<< object.B::i;
std::memset((void*)&(A&)object ,0,sizeof(A));
std::cout<<object.B::i;
}
// outputs 10 with g++/clang++, c++11, Debian 8, amd64
因此,可以想象问题栏中的内存集可能将a [1] .i设为零,这样程序将输出0而不是3。
在极少数情况下,根本不会在C ++对象中使用类似memset的函数。 (通常,如果这样做,子对象的析构函数会公然失败。)但是有时人们希望在其析构函数中清除“几乎POD”类的内容,这可能是例外。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.