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

What is the memory layout of vector of arrays?

谁能解释一下

的内存布局
std::vector<std::array<int, 5>> vec(2)

是否提供二维数组的连续内存块 有 2 行 5 个元素?

据我了解,向量中的向量

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

提供two 连续数组长度 5个元素s 在内存中的不同位置

数组的向量是否相同?

数组没有任何间接寻址,只是存储它们的数据"directly"。也就是一个std::array<int, 5>字面上连续包含五个int,平整。而且,像向量一样,它们不会在元素之间放置填充,所以它们是 "internally contiguous".

但是,the std::array object itself may be larger than the set of its elements!允许有尾随 "stuff" 之类的填充。因此,尽管有可能,在第一种情况下,您的数据 所有 不一定是连续的。

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::vectorstd::array 之间的最大区别在于 std::vector 包含一个指向它包装的内存的指针,而 std::array 本身包含实际的数组。

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

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

最后一位还意味着 std::array 的数组(纯 C 风格或 std::array)也可能不会连续保存数据。数组中的 std::array 个对象将是连续的,但数据不是。

保证 "multi-dimensional" 数组数据连续的唯一方法是嵌套纯 C 风格数组。

C++ 标准不保证 std::array 在数组末尾不包含任何有效载荷,所以你不能假设后续数组的第一个元素就在最后一个元素之后一个先验数组。

即使是这种情况,尝试通过指向不同数组中元素的指针的指针算术到达数组中任何元素的行为也是未定义的。这是因为指针运算只在数组中有效。

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

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

以上内容减轻了 std::array 末尾的任何填充。到目前为止,没有主要的编译器会导致上述失败,我敢打赌将来也不会。

当且仅当上述失败时,std::vector<std::array<int,5>> v(2) 将在 std::array 之间有一个 "gap"。

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

int* ptr = &v[0][0];

只有 ptr+5 的有效域,取消引用 ptr+5 是未定义的行为。

这是由于别名规则;你不允许 "walk" 越过一个对象的末尾进入另一个对象,即使你知道它在那里,除非你首先往返于某些类型(如 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] 也未在结果中指定,即使它们的二进制值在每个主要硬件系统上的每个编译器中绝对相同。

如果您想更进一步,由于对指针别名的这些限制,甚至无法在 C++ 本身内实现 std::vector<int>。最后我检查了(在 之前,但我没有看到 C++17 中的决议)标准委员会正在努力解决这个问题,但我不知道任何此类努力的状态。 (这没有你想象的那么严重,因为没有什么需要 std::vector<int> 在符合标准的 C++ 中实现;它必须简单地具有标准定义的行为。它可以在内部使用特定于编译器的扩展。)