std :: vector在内存中看起来像什么?

时间:2018-09-14 10:24:58

标签: c++ memory vector contiguous

我读到std::vector应该是连续的。我的理解是,它的元素应该存储在一起,而不是散布在整个内存中。我只是接受了这一事实,并在例如使用其data()方法获取底层连续内存时使用了该知识。

但是,我遇到了一种情况,矢量的记忆以一种奇怪的方式表现:

std::vector<int> numbers;
std::vector<int*> ptr_numbers;
for (int i = 0; i < 8; i++) {
    numbers.push_back(i);
    ptr_numbers.push_back(&numbers.back());
}

我希望这能给我一些数字的向量和指向这些数字的指针的向量。但是,在列出ptr_numbers指针的内容时,会有不同的看似随机的数字,好像我正在访问错误的内存部分。

我试图检查每一步的内容:

for (int i = 0; i < 8; i++) {
    numbers.push_back(i);
    ptr_numbers.push_back(&numbers.back());
    for (auto ptr_number : ptr_numbers)
       std::cout << *ptr_number << std::endl;
    std::cout << std::endl;
}

结果大致如下:

1

some random number
2

some random number
some random number
3

因此,似乎当我push_back()numbers向量时,它的旧元素会更改其位置。

那么std::vector是一个连续的容器到底是什么意思,为什么它的元素会移动?是否需要将它们存储在一起,但是在需要更多空间时将它们一起移动?

编辑:std::vector仅在C ++ 17之后才是连续的吗? (只是保持对我以前的主张的评论与未来的读者相关。)

3 个答案:

答案 0 :(得分:5)

  

那么std :: vector是一个连续的容器到底是什么意思,为什么它的元素会移动?是否需要将它们存储在一起,但是在需要更多空间时将它们一起移动?

这就是它的工作方式,为什么在发生重新分配时附加元素确实会使所有迭代器以及内存位置无效¹。这不仅从C ++ 17开始有效,从那以后一直如此。

这种方法有很多好处:

  • 它非常易于缓存,因此非常有效。
  • data()方法可用于将基础原始内存传递给使用原始指针的API。
  • push_backreserveresize上分配新内存的成本归结为恒定时间,因为几何增长会随着时间的流逝而摊销(每次称为push_back在libc ++和libstdc ++中,容量增加了一倍,在MSVC中大约增加了1.5倍。
  • 它允许使用最受限制的迭代器类别,即随机访问迭代器,因为当数据连续存储时,经典指针算法效果很好。
  • 从另一个实例中移动向量实例的构造非常便宜。

这些含义可以视为这种内存布局的缺点:

  • 所有对元素进行迭代的迭代器和指针在对向量进行隐式重新分配后都将失效。这可能会导致细微的错误,例如遍历向量的元素时删除元素。
  • 未提供push_front(提供std::liststd::deque之类的操作(insert(vec.begin(), element)有效,但可能很昂贵¹),以及有效的合并/拼接多个向量实例。

¹感谢@FrançoisAndrieux指出这一点。

答案 1 :(得分:1)

就实际结构而言,std::vector在内存中看起来像这样:

struct vector {    // Simple C struct as example (T is the type supplied by the template)
  T *begin;        // vector::begin() probably returns this value
  T *end;          // vector::end() probably returns this value
  T *end_capacity; // First non-valid address
  // Allocator state might be stored here (most allocators are stateless)
};

Relevant code snippet from the libc++ implementation as used by LLVM

打印std::vector的原始内存内容:
(如果您不知道自己在做什么,请不要这样做!)

#include <iostream>
#include <vector>

struct vector {
    int *begin;
    int *end;
    int *end_capacity;
};

int main() {
    union vecunion {
        std::vector<int> stdvec;
        vector           myvec;
        ~vecunion() { /* do nothing */ }
    } vec = { std::vector<int>() };
    union veciterator {
        std::vector<int>::iterator stditer;
        int                       *myiter;
        ~veciterator() { /* do nothing */ }
    };

    vec.stdvec.push_back(1); // Add something so we don't have an empty vector

    std::cout
      << "vec.begin          = " << vec.myvec.begin << "\n"
      << "vec.end            = " << vec.myvec.end << "\n"
      << "vec.end_capacity   = " << vec.myvec.end_capacity << "\n"
      << "vec's size         = " << vec.myvec.end - vec.myvec.begin << "\n"
      << "vec's capacity     = " << vec.myvec.end_capacity - vec.myvec.begin << "\n"
      << "vector::begin()    = " << (veciterator { vec.stdvec.begin() }).myiter << "\n"
      << "vector::end()      = " << (veciterator { vec.stdvec.end()   }).myiter << "\n"
      << "vector::size()     = " << vec.stdvec.size() << "\n"
      << "vector::capacity() = " << vec.stdvec.capacity() << "\n"
      ;
}

答案 2 :(得分:-1)

如果尝试以这种方式进行编码,则会发现值保持不变,并且向量中每个值的地址与其相邻元素的差值为4(有趣)。

std::vector<int> numbers;
std::vector<int*> ptr_numbers;

// adding values 0 up to 8 in the vector called numbers
for (int i = 0; i < 8; i++) {
    numbers.push_back(i);

}

// printing out the values inside vector numbers 
//and storing the address of each element of vector called numbers inside the ptr_numbers.
for (int i = 0; i != numbers.size(); i++) {
    cout << numbers[i] << endl;
    ptr_numbers.push_back(&numbers[i]);
}
cout << "" << endl;

// printing out the values of each element of vector ptr_numbers
for (int y = 0; y != ptr_numbers.size(); y++) {
    cout << *ptr_numbers[y] << endl;
}

// printing out the address of each element of vector ptr_numbers
for (int y = 0; y != ptr_numbers.size(); y++) {
    cout << &ptr_numbers[y] << endl;
}

当您遍历两个向量时。它们将输出相同的值。