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 而導致問題

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