向量数组的内存布局是什么?

时间:2019-01-15 10:39:05

标签: c++ c++11 language-lawyer stdvector stdarray

谁能解释一下

std::vector<std::array<int, 5>> vec(2)

是否提供2D阵列的连续存储块 有2行5个元素?

据我所知,向量的向量

std::vector<std::vector<int>> vec(2, std::vector<int>(5))

在内存中提供两个 长度为5个元素的 个连续数组的长度。

数组的向量会相同吗?

4 个答案:

答案 0 :(得分:58)

数组没有任何间接的,只是“直接”存储它们的数据。也就是说,std::array<int, 5>字面连续包含五个int。而且,像矢量一样,它们不会在元素之间放置填充,因此它们是“内部连续的”。

但是,the std::array object itself may be larger than the set of its elements!允许具有尾随的“填充”(例如填充)。因此,尽管可能,但在第一种情况下,数据全部不一定是连续的。

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的向量将更清晰,完全打包,并且使用起来可能更安全。

对于向量的向量,向量实际上只是一个指针加上一些内部管理,因此是间接的(如您所说)。

答案 1 :(得分:18)

std::vectorstd::array之间的最大区别在于,std::vector包含一个指向它包装的内存的指针,而std::array本身包含了一个实际的数组。

这意味着向量的向量就像jagged array

对于数组的向量,std::array对象将连续放置,但要与向量对象分开。请注意,std::array对象本身可能大于其包含的数组,如果是,则数据将不是连续的。

最后一位还表示std::array的数组(纯C样式或std::array)也可能不会连续保留数据。数组中的std::array对象是连续的,但数据不是连续的。

保证“多维”数组连续数据的唯一方法是嵌套的普通C样式数组。

答案 2 :(得分:11)

C ++标准不保证std::array在数组的末尾不包含任何有效载荷,因此cannot,您不能假定后续数组的第一个元素紧随前一个数组的最后一个元素之后数组。

即使是这种情况,通过指向另一个数组中元素的指针的指针算术尝试到达数组中任何元素的行为也是不确定的。这是因为指针算术仅在数组内有效。

以上内容也适用于std::array<std::array>

答案 3 :(得分:6)

static_assert(sizeof(std::array<int,5>)==5*sizeof(int));

以上内容可避免在std::array的末尾添加任何填充。到目前为止,没有任何主要的编译器会导致以上操作失败,我敢打赌将来不会。

当且仅当以上操作失败时,std::vector<std::array<int,5>> v(2)之间的std::array之间才会有一个“间隙”。

这并没有您想要的那么多;指针生成如下:

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*做什么,您都不能合法地修改xz

*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]的二进制值在每个主要硬件系统上的每个编译器中绝对相同,也不会在结果中指定比较std::vector<int>

如果您想进一步深入研究,由于指针别名的这些限制,甚至不可能在C ++本身中实现std::vector<int>。最后我检查了(在之前,但是我没有在C ++ 17中看到解决方案)标准委员会正在努力解决这一问题,但是我不知道这种努力的状态。 (这没有您想像的那样麻烦,因为没有要求在符合标准的C ++中实现watch;它必须简单地具有标准定义的行为。它可以在内部使用特定于编译器的扩展。)