矢量中的动态内存分配
Dynamic memory allocation in Vector
我对向量(STL - C++)中的内存分配有疑问。据我所知,每当向量的大小等于其容量时,它的容量都会动态翻倍。如果是这样的话,为什么分配是连续的?它如何仍然允许像数组一样使用 [] 访问运算符进行 O(1) 访问?谁能解释这种行为? (列表也有动态内存分配,但我们无法使用 [] 访问运算符访问其元素,矢量怎么可能?
#include<iostream>
#include<vector>
using namespace std;
int main() {
// your code goes here
vector<int> v;
for(int i=0;i<10;i++){
v.push_back(i);
cout<<v.size()<<" "<<v.capacity()<<" "<<"n";
}
return 0;
}
输出:
11
2 2 3
4 4
4 5
8 6 8 7 8 8 8
8 9
16
10 16
据我所知,每当向量的大小等于其容量时,它的容量就会动态地加倍。
它不需要像您的情况那样加倍,它是定义的实现。因此,如果您使用其他编译器,则可能会有所不同。
如果是这样的话,为什么分配是连续的?
如果向量可以分配的连续内存不再多,则向量必须将其数据移动到满足其大小要求的新连续内存块。旧块将被标记为自由,以便其他人可以使用它。
它如何仍然允许像数组一样使用 [] 访问运算符进行 O(1) 访问?
由于之前所说的事实,可以通过[] operator
或pointer + offset
访问。对数据的访问将是 O(1)。
List 也有动态内存分配,但我们无法使用 [] 访问运算符访问其元素,矢量怎么可能?
列表(例如 std::list)与 std::vector 完全不同。在 C++ std::list 的情况下,它保存节点和数据、指向下一个节点的指针和指向前一个节点的指针(双链表)。因此,您必须浏览列表才能获得所需的特定节点。 向量的工作方式如上所述。
向量必须将对象存储在一个连续的内存区域中。因此,当它需要增加其容量时,它必须分配一个新的(更大的)内存区域(或者扩展它已经拥有的内存区域,如果可能的话),并将对象从"旧的,小"的区域复制或移动到新分配的区域。
这可以通过使用带有带有具有一些副作用的复制/移动构造函数的类来变得明显(ideone 链接):
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
#define V(p) static_cast<void const*>(p)
struct Thing {
Thing() {}
Thing(Thing const & t) {
cout << "Copy " << V(&t) << " to " << V(this) << endl;
}
Thing(Thing && t) /* noexcept */ {
cout << "Move " << V(&t) << " to " << V(this) << endl;
}
};
int main() {
vector<Thing> things;
for (int i = 0; i < 10; ++i) {
cout << "Have " << things.size() << " (capacity " << things.capacity()
<< "), adding another:n";
things.emplace_back();
}
}
这将导致输出类似于
[..]
Have 2 (capacity 2), adding another:
Move 0x2b652d9ccc50 to 0x2b652d9ccc30
Move 0x2b652d9ccc51 to 0x2b652d9ccc31
Have 3 (capacity 4), adding another:
Have 4 (capacity 4), adding another:
Move 0x2b652d9ccc30 to 0x2b652d9ccc50
Move 0x2b652d9ccc31 to 0x2b652d9ccc51
Move 0x2b652d9ccc32 to 0x2b652d9ccc52
Move 0x2b652d9ccc33 to 0x2b652d9ccc53
[..]
这表明,当向量添加第三个对象时,它已经包含的两个对象从一个连续区域(查看 1 (sizeof(Thing)
) 递增地址移动到另一个连续区域。最后,在添加第五个对象时,您可以看到第三个对象确实直接放置在第二个对象之后。
它何时移动,何时复制?当移动构造函数标记为noexcept
时,将考虑移动构造函数(或者编译器可以推断出它)。否则,如果允许抛出,向量最终可能会处于这样的状态:其对象的一部分位于新的内存区域中,但其余部分仍在旧内存区域中。
这个问题应该在两个不同的层面上考虑。
从标准的角度来看,需要提供一个连续存储,以允许程序员使用其第一个元素的地址作为数组第一个元素的地址。当您通过重新分配仍然保留以前的元素来添加新元素时,需要让它的容量增长 - 但它们的地址可能会改变。
从实现的角度来看,它可以尝试就地扩展分配的内存,如果不能,则分配一个全新的内存,并在新的分配内存区域中移动或复制构造现有元素。标准未指定大小增加,留给实现。但你是对的,每次将分配的大小加倍是常见的用法。
- Win32编译器选项和内存分配
- 多个文件的内存分配错误"在抛出 'std :: bad_alloc' what (): std :: bad_alloc 的实例后终止调用" [C++]
- 当需要超过16GB的连续内存时,内存分配失败
- 尝试摆脱任何堆内存分配
- 以下代码执行哪种内存分配(动态或静态)?
- 开放 CV 中的动态内存分配,用于视频处理
- 为什么类和 main() 函数中也有动态内存分配
- 使用 NTAllocateVirtualMemory 和 GetProcAddress 的内存分配问题不起作用
- C++:矢量分配器行为、内存分配和智能指针
- 介于 [固定数组] 和 [带内存分配的指针] 之间的性能
- Linux C++ 中的页面对齐内存分配
- 整数内存分配/释放
- 将内存分配返回值强制转换为 TYPE 数组
- C++程序什么都不做,但瓦尔格林德显示内存分配
- 给定特定内存地址的数组的动态内存分配
- 如何完成内存分配
- 我刚刚了解了C++中的动态内存分配
- 在先前调用 string::find 后添加内存分配和内存集会导致它返回 npos.为什么?
- 对于堆上的页面对齐内存分配是否有任何优化或不同的 API?
- 无法删除布尔动态内存分配