非连续对象的数组
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)
将标准保证弱化到程序可能显示未定义行为的程度的理由是什么?
标准规定:数组类型的对象包含连续分配的N个子对象的非空集合,子对象类型为t。(dcl。数组)§8.3.4。如果类型T的对象不占用连续存储,那么这种对象的数组如何占用连续存储呢?
编辑:删除可能分散注意力的说明文字
1。这是实际编写编译器的巨龙们采用的奥卡姆剃刀的一个实例:不要提供比解决问题所需更多的保证,否则您的工作量将在没有补偿的情况下翻倍。复杂的类适应花哨的硬件或历史悠久的硬件是问题的一部分。(由BaummitAugen和M.M暗示)
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[1]来强制不相邻的对象进入相邻的布局;代替T T;
现在如何解决这个混乱?在标准中,sizeof()操作符的定义从根本上是模糊的,这似乎是至少在每个体系结构中,类型大致等于布局的时代的遗留物,而现在已经不是这样了。(placement new如何知道该创建哪个布局?)
当应用于一个类时,[of sizeof()]的结果是该类对象的字节数,包括在数组中放置该类型对象所需的任何填充。[expr。§5.3.3 (2)
但是等等,所需的填充量取决于布局,单个类型可能有多个布局。所以我们必须在所有可能的布局中选择最小值,或者做一些同样武断的事情。
最后,如果这是预期的含义,那么数组定义将受益于char*算术方面的消歧。否则,问题1的答案也适用。
与现在删除的答案和评论有关的一些备注:正如在技术上对象可以占用非连续字节存储中所讨论的那样?,不连续对象实际上是存在的。此外,简单地对子对象进行memset可能会使包含对象的不相关的子对象无效,即使是对于完全连续的、简单可复制的对象也是如此:
#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
因此,可以想象题帖中的memset可能为0 a[1]。I,这样程序将输出0而不是3。
在c++对象中使用类memset函数的情况很少。(通常,如果这样做,子对象的析构函数会明显失败。)但有时人们希望在析构函数中清除'almost-POD'类的内容,这可能是例外。
- 销毁C++中动态分配的内存(数组对象)
- 数组对象的生存期是否在重用其元素存储时结束?
- 为什么顶点数组对象会导致错误?
- 具有纯虚函数和指针数组对象类型的父类的指针数组
- 这是使用构造函数初始化数组对象的最佳方法吗?
- OpenGL 顶点数组对象与 tinyobjloader
- 将数组/对象/结构列表从C#库中传递给C MFC应用程序
- C++ RapidJson 帮助反序列化数组对象
- ptrdiff_t可以表示指向同一数组对象元素的指针的所有减法吗?
- 检查成员函数是否返回临时对象或数组对象
- 为什么 std::variant 不能容纳数组对象类型,而联合可以?
- 当数组对象以函数参数传递时,为什么复制构造函数会自称
- 如何使用箭头指针打印出一类数组对象,这些对象中有多个分数
- C++17 std::shared_ptr<> 类数组对象的重载运算符 []
- 添加两个具有运算符重载的数组对象,从而导致分段错误
- opengl:两个不同的矢量可以绑定到同一个顶点数组对象吗
- 使用相同的数据填充数组对象或使用指针
- 方法用于最快的分配,并且不需要将动态大小的数组对象作为局部变量
- 如何将2d数组对象传递给c++中的函数
- ReferenceTable溢出(jni-android),数组对象释放