任何人都能解释的内存布局吗
std::vector<std::array<int, 5>> vec(2)
它提供2D阵列的连续内存块吗有2行5个元素?
据我所知,向量中的向量
std::vector<std::vector<int>> vec(2, std::vector<int>(5))
提供两个连续阵列的内存布局,这两个阵列的长度为5个元素s,位于内存中的不同位置。
数组的向量会是一样的吗?
数组没有任何间接寻址,只是"直接"存储它们的数据。也就是说,一个std::array<int, 5>
实际上包含五个连续的、平坦的int
。而且,像向量一样,它们不会在元素之间添加填充,因此它们是"内部连续的"。
但是,std::array
对象本身可能大于其元素集!允许有尾随的"东西",如填充。因此,尽管很可能,但在第一种情况下,您的数据将所有都是连续的并不一定是真的。
An int
+----+
| |
+----+
A vector of 2 x int
+----+----+----+-----+ +----+----+
| housekeeping | ptr | | 1 | 2 |
+----+----+----+-----+ +----+----+
| ^
-----------
An std::array<int, 5>
+----+----+----+----+----+----------->
| 1 | 2 | 3 | 4 | 5 | possible cruft/padding....
+----+----+----+----+----+----------->
A vector of 2 x std::array<int, 5>
+----+----+----+-----+ +----+----+----+----+----+----------------------------+----+----+----+----+----+----------->
| housekeeping | ptr | | 1 | 2 | 3 | 4 | 5 | possible cruft/padding.... | 1 | 2 | 3 | 4 | 5 | possible cruft/padding....
+----+----+----+-----+ +----+----+----+----+----+----------------------------+----+----+----+----+----+----------->
| ^
-----------
而且,即使是这样,由于混叠规则,您是否能够使用单个int*
来导航所有10个数字也可能是另一回事!
总之,十个int
的矢量会更清晰、完全封装,使用起来可能更安全。
在向量中的一个向量的情况下,向量实际上只是一个指针加上一些内务管理,因此是间接的(正如你所说)。
std::vector
和std::array
之间的最大区别在于,std::vector
包含一个指向其封装的内存的指针,而std::array
本身包含实际的数组。
这意味着一个向量的向量就像一个锯齿状的数组。
对于数组的矢量,std::array
对象将连续放置,但与矢量对象分离。请注意,std::array
对象本身可能大于它们所包含的数组,如果是,则数据将不连续。
最后一位还意味着std::array
的数组(普通C样式或std::array
)也可以不连续地保持数据。数组中的std::array
对象将是连续的,但不是数据。
保证"多维"数组的连续数据的唯一方法是嵌套的纯C样式数组。
C++标准不能保证std::array
在数组末尾不包含任何有效负载,因此您不能假设后续数组的第一个元素刚好在前一个数组的最后一个元素之后。
即使是这种情况,试图通过对指向不同数组中元素的指针进行指针运算来访问数组中任何元素的行为也是未定义的。这是因为指针算术只在数组中有效。
以上内容也适用于CCD_ 16。
static_assert(sizeof(std::array<int,5>)==5*sizeof(int));
以上减轻了在CCD_ 17的末端上具有任何填充的问题。到目前为止,没有一个主要的编译器会导致上面的失败,我敢打赌将来也不会。
如果且仅当上述失败,则std::vector<std::array<int,5>> v(2)
将在std::array
s之间存在"间隙"。
这并没有你想要的那么多帮助;如下生成的指针:
int* ptr = &v[0][0];
仅具有高达ptr+5
的有效域,并且取消引用ptr+5
是未定义的行为。
这是由于混叠规则造成的;您不允许从一个对象的末尾"走过"到另一个对象,即使您知道它在那里,除非您首先往返于某些类型(如char*
),在这些类型中,允许较少限制的指针运算。
反过来,该规则的存在允许编译器推断通过哪个指针访问哪些数据,而不必证明任意指针算法会让您访问外部对象。
因此:
struct bob {
int x,y,z;
};
bob b {1,2,3};
int* py = &b.y;
无论您将py
作为int*
进行什么操作,都不能用它合法地修改x
或z
。
*py = 77;
py[-1]=3;
std::cout << b.x;
编译器可以优化std::cout
行以简单地打印1
,因为py[-1]=3
可能尝试修改b.x
,但通过这种方式这样做是未定义的行为。
同样的限制会阻止您从std::vector
中的第一个数组转到第二个数组(即超过ptr+4
)。
创建ptr+5
是合法的,但只能作为越过结束指针的一个。比较ptr+5 == &v[1][0]
也没有在结果中指定,即使它们的二进制值在每个主要硬件系统上的每个编译器中都绝对相同。
如果您想深入研究兔子洞,由于指针别名的这些限制,甚至不可能在C++本身中实现std::vector<int>
。上次我检查了一下(那是在c++17之前,但我在c++17中没有看到决议)标准委员会正在努力解决这个问题,但我不知道任何这样的努力的状态。(这比你想象的问题要小,因为没有什么要求std::vector<int>
在符合标准的C++中实现;它必须有标准定义的行为。它可以在内部使用编译器特定的扩展。)