vector push_back 超过 capacity 时产生的内存拷贝问题

根据 C++ Reference 的官方解释:http://www.cplusplus.com/reference/vector/vector/

Just like arrays, vectors use contiguous storage locations for their elements, which means that their elements can also be accessed using offsets on regular pointers to its elements, and just as efficiently as in arrays. But unlike arrays, their size can change dynamically, with their storage being handled automatically by the container.
Internally, vectors use a dynamically allocated array to store their elements. This array may need to be reallocated in order to grow in size when new elements are inserted, which implies allocating a new array and moving all elements to it. This is a relatively expensive task in terms of processing time, and thus, vectors do not reallocate each time an element is added to the container.

从此可以看出,std::vector 具有以下特点:

  • 内部使用数组的方式进行数据存储,内存连续
  • 容量支持动态扩展,扩展时需要将原数组的内容拷贝到新 allocate 的数组中

这篇文章就 vector 扩展时的情况进行简析,防止踩坑。


size 和 capacity

vector 中有两个和大小有关的变量:

  • size:vector 中实际存储的容量大小
  • capacity:vector 中当前支持的容量,在 0 ~ capacity 这个范围内,都可以使用数组索引的方式直接访问和修改;当 size 达到 capacity 时,只能通过 push_back 进行插入,此时会进行扩展,capacity 会增大

扩展的时机

当 vector 的 size 达到 capacity 时,当前 vector 预留的连续内存已经完全写满,此时如果通过 push_back 进行插入操作时,会导致该 vector 进行扩展。

扩展前后的变化

  • 扩展后,capacity 会增大
  • 扩展时会进行拷贝,原始数据会被 copy 到新的数组中,原数组会被 free

我们来看个例子:

#include <iostream>
#include<vector>
#include<string>

using namespace std;

// 打印元素
void print_vector(vector<string>& vec)
{
  for(size_t i = 0; i < vec.size(); i++) {
    cout << vec[i] << " ";
  }
  cout << endl;
}

int main()
{
  vector<string> vec;
  // 设定 vec 的 capacity 为2.
  vec.reserve(2);

  vec.push_back("1");
  vec.push_back("2");

  cout << "before extension: size=" << vec.size() << ", cap=" << vec.capacity() << endl;
  print_vector(vec);

  // 再插入一个元素
  vec.push_back("3");

  cout << "after extension: size=" << vec.size() << ", cap=" << vec.capacity() << endl;
  print_vector(vec);

  return 0;
}

运行上面的代码,可以发现有如下打印:

before extension: size=2, cap=2
1 2 
after extension: size=3, cap=4
1 2 3 

这说明,在 vec.push_back("3"); 这条执行后,size 由 2 变成了 3,这是因为实际存储的数据也变成了 3 个;capacity 由 2 变成了 4,这验证了 vector 在这次 push_back 时进行了扩容,并且容量变成了原来的两倍。并且,通过打印我们可以看到,实际存储的数据在 push_back 前后符合我们的预期。

前面提到,vector 会在容量扩展时将原数组中的内存拷贝到新的数组中,我们通过修改上面的 print_vector 函数,来进一步验证这里存在拷贝:

// 打印元素
void print_vector(vector<string>& vec)
{
  for(size_t i = 0; i < vec.size(); i++) {
    // 打印 vec 中元素的地址
    cout << &vec[i] << " ";
  }
  cout << endl;
}

修改后运行,可以得到如下结果:

before extension: size=2, cap=2
0x8d8c20 0x8d8c40 
after extension: size=3, cap=4
0x8d9080 0x8d90a0 0x8d90c0

我们可以看出,vec 中存储的前两个元素,也就是 string 类型的 “1” 和 “2” 的地址发生了改变。而且对于 0x8d9080 0x8d90a0 0x8d90c0 这3个地址而言,间隔是十进制的 32,刚好是 string 占用的 32 个字节大小。因此,这也验证了 vector 中内存是连续的。

扩展时应该注意的问题

由于 vector 扩展时存在拷贝,并且原来被拷贝的内存会被 free,因此我们一定要注意浅拷贝和深度拷贝的区别,防止指针被拷贝但指针指向的内存由于这次扩容被 free 而导致问题

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章