代码之家  ›  专栏  ›  技术社区  ›  egst

std::vector在内存中是什么样子的?

  •  38
  • egst  · 技术社区  · 6 年前

    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 向量,它的旧元素会改变它们的位置。

    标准::向量

    编辑:是 标准::向量

    5 回复  |  直到 6 年前
        1
  •  69
  •   Vittorio Romeo    6 年前

    大致看起来是这样的(请原谅我的绘画杰作):

    vector memory layout

    这个 std::vector 堆栈上的实例是一个小对象,其中包含指向堆分配的缓冲区的指针,再加上一些额外的变量来跟踪向量的大小和容量。


    所以当我 push_back() numbers 向量,它的旧元素会改变它们的位置。

    新建缓冲区 将被分配到堆的其他地方,并且所有以前的元素都将被移到新的元素中。因此,他们的地址将会改变。


    大致来说,是的。元素的迭代器和地址稳定性由 没有发生重新分配。


    我知道 标准::向量

    标准::向量 自从它第一次出现在标准中就没有改变。 ContiguousContainer 只是一个“概念”,添加它是为了在编译时将连续容器与其他容器区分开来。

        2
  •  14
  •   bobah    6 年前

    它是一个单一的连续存储器(1d阵列)。 每次它的容量耗尽时,它就会被重新分配,存储的对象会被移动到新的更大的地方——这就是为什么您会观察到存储对象的地址在变化。

    C++17 .

    存储正在增长 几何学 O(1) push_back() . 生长因子是2( n+1个 =上限 +帽 n 在C++标准库的大多数实现中( GCC , Clang , STLPort )和1.5( n+1个 +帽 n /2 )在 MSVC 变体。

    growing std::vector

    vector::reserve(N) 而且足够大 N ,则添加新对象时存储对象的地址不会更改。

    在大多数实际应用程序中,通常需要将它预先分配给至少32个元素,以跳过紧随其后的前几个重新分配(0→1→2→4→8→16)。

    =上限 n ),或在某个合理的较大大小后完全停止,以确保应用程序不会浪费或耗尽内存。

    std::deque 但大块要大得多)。通过这种方式,可以为每列和每行查询合理地存储数据(尽管这也需要内存分配器的帮助)。

        3
  •  7
  •   nos    6 年前

    std::vector 作为一个连续的容器意味着你认为它意味着什么。

    然而,对向量的许多操作可以重新定位整个内存。

    一种常见的情况是,当您向其中添加元素时,向量必须增长,它可以将所有元素重新分配并复制到另一个连续的内存块中。

        4
  •  5
  •   lubgr    6 年前

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

    这正是它的工作原理,也是为什么当重新分配发生时,附加元素确实会使所有迭代器以及内存位置失效。这不仅是有效的,因为C++ 17,它一直以来的情况。

    这种方法有几个好处:

    • 它对缓存非常友好,因此效率很高。
    • 这个 data() 方法可用于将底层原始内存传递给使用原始指针的API。
    • push_back , reserve resize 在libc++和libstdc++中,容量是原来的两倍,在MSVC中大约增长了1.5倍。
    • 从一个向量实例到另一个向量实例的Move构造非常便宜。

    • 所有指向元素的迭代器和指针在向量修改后都将失效,这意味着重新分配。这可能会导致一些细微的错误,例如在遍历向量的元素时删除元素。
    • 操作如 push_front std::list std::deque insert(vec.begin(), element) 工作,但可能是昂贵的),以及多个向量实例的有效合并/拼接。

        5
  •  2
  •   yyny    6 年前

    就实际结构而言 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

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

    #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"
          ;
    }
    
        6
  •  -2
  •   Tushar Devi    5 年前

    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;
    }
    

    当你在两个向量之间循环时。它们将输出相同的值。