数组向量的内存布局是啥?
Posted
技术标签:
【中文标题】数组向量的内存布局是啥?【英文标题】:What is the memory layout of vector of arrays?数组向量的内存布局是什么? 【发布时间】:2019-06-09 08:48:22 【问题描述】:谁能解释一下内存布局
std::vector<std::array<int, 5>> vec(2)
它是否提供二维数组的连续内存块 有 2 行,每行 5 个元素?
据我所知,向量的向量
std::vector<std::vector<int>> vec(2, std::vector<int>(5))
提供两个 长度连续数组 5个元素在内存中的不同位置的内存布局。
数组的向量会一样吗?
【问题讨论】:
给出答案,如果你想要这个,请使用std::vector<int> vec(5*2)
并在平面一维数组中自己进行二维索引。也许在平面容器上编写一个用于二维索引的包装类,具有模板化或运行时可变的行长度。您还希望展示一个平面视图,以便算法只需要对每个元素执行某些操作而不关心 2D 位置,可以通过一个大循环更有效地完成此操作。
【参考方案1】:
数组没有任何间接性,只是“直接”存储它们的数据。也就是说,std::array<int, 5>
字面上连续包含五个int
s,平坦。而且,与向量一样,它们不会在元素之间添加填充,因此它们是“内部连续的”。
但是,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
s 的向量会更清晰、更完整,并且使用起来可能更安全。
在向量的向量的情况下,向量实际上只是一个指针加上一些内务,因此是间接的(如你所说)。
【讨论】:
根据这里的答案,数据不必是连续的:Is the data in nested std::arrays guaranteed to be contiguous?。关于这个话题有一些讨论。另一个讨论:Does std::array of std::array have contiguous memory? 和 Is the size of std::array defined by standard。 IOW,虽然分配的内存必须是连续的,但数组元素不必连续。 哦,这个答案变得越来越时髦了。最多 13 个。 @Bathsheba 该图令人震惊,但哦,好吧? 注意static_assert(sizeof(std::array<int,t>)==sizeof(int)*5)
减轻了任何填充(并传入支持std::array
的每个主要编译器的每个版本)。它不能缓解混叠问题。【参考方案2】:
std::vector
和std::array
之间的最大区别在于std::vector
包含指向它所包装的内存的指针,而std::array
本身包含实际的数组。
这意味着向量的向量就像jagged array。
对于数组向量,std::array
对象将连续放置,但与向量对象分开。请注意,std::array
对象本身可能比它们包含的数组大,如果是这样,那么数据将不连续。
最后一位也意味着std::array
的数组(纯C 样式或std::array
)也可能不会连续保持数据。数组中的std::array
对象将是连续的,但不是数据。
保证“多维”数组的连续数据的唯一方法是嵌套的纯 C 样式数组。
【讨论】:
这也意味着数组的向量与数组的数组类似,因为数据都是连续的......我敢不同意。请在 LightnessRacesinOrbit 的回答下查看我的评论。 @DanielLangr 感谢您提醒我。改写了那部分。【参考方案3】:C++ 标准不保证std::array
在数组末尾不包含任何有效负载,因此您不能假设后续数组的第一个元素就在前一个数组的最后一个元素之后。
即使是这种情况,通过指针算术对指向不同数组中元素的指针尝试到达数组中任何元素的行为是未定义的。这是因为指针算法只在数组中有效。
以上内容也适用于std::array<std::array>
。
【讨论】:
【参考方案4】:static_assert(sizeof(std::array<int,5>)==5*sizeof(int));
以上内容可以避免在 std::array
的末尾添加任何填充。到目前为止,没有主要编译器会导致上述失败,我敢打赌将来不会。
当且仅当上述失败时,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++ 中实现;它必须简单地具有标准定义的行为。它可以在内部使用特定于编译器的扩展。)
【讨论】:
不错的答案;提高了。还要注意一些相关的问题,即您不能在标准 C 中编写 malloc。以上是关于数组向量的内存布局是啥?的主要内容,如果未能解决你的问题,请参考以下文章