C++STL(23) 研究std::vector的内存增长规律

Posted 奇妙之二进制

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++STL(23) 研究std::vector的内存增长规律相关的知识,希望对你有一定的参考价值。

一、前言

首先说明,vector可以理解为动态数组,既然是数组,那么它在内存中就应该是一块连续的内存,但vector是如何支持动态增长的呢?关于这个问题,网上有很对说法,但其中有些说法是错误的,最近看到有一篇博友的解释就非常正确vector空间动态增长,这里就针对的他的解释进行进一步的阐述,并进行实际验证。

二、vector内存增长方式

C++primer中多次明确指出对vector使用的建议是:先创建一个空的vector对象,然后再运行时再利用vector的成员函数push_back向其中添加元素。先以此来验证vector的内存的自动增长,程序说明为:创建一个空的vector a,然后采用push_back向其中添加元素,共迭代10次,运行环境为g++。

#include <iostream>
#include <vector>
using namespace std;
int main()

    vector<int> a;
    cout << "a.size(): " << a.size() << "	a.capacity(): " << a.capacity() << endl;
    for (int i = 0; i < 10; i++)
    
        a.push_back(i);
        cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    
    return 0;

运行结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Te1t6q0r-1637481964249)(C:\\Users\\小静静\\AppData\\Roaming\\Typora\\typora-user-images\\image-20211121155312405.png)]

在分析结果之前,首先介绍与vector的内存布局,然后再介绍与内存增长相关的四个函数,分别为size()、capacity()、reserve()、resize()函数。

(1)vector内存布局

start迭代器指向已用空间的首元素,finish指向已用空间的尾元素的下一个位置,end_of_storage指向可用空间的末尾。

(2)size()、capacity()、reserve()、resize()函数

  • size()函数返回的是已用空间大小,

  • capacity()返回的是总空间大小,

  • capacity()-size()则是剩余的可用空间大小。

当size()和capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长。

  • reserve(n)预先分配一块较大的指定大小的内存空间,其中n为分配空间大小;
  • resize()成员函数只改变元素的数目,不改变vector的容量。

再看刚刚的运行结果,首先可以确定,在VS2010的编译器里面每次并不是增长固定的内存,可以看出是增长当前内存的一倍。而且由于我们的程序没有调用reserve(n)函数预先分配一块内存,所以内存增长是编译器自动完成的。这个自动增长包括重新分配内存空间、拷贝原空间、释放原空间三个过程,具体策略为当添加元素时,如果vector空间大小不足,则会以原大小的2倍另外配置一块较大的新空间,然后将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间。也就是说vector的空间动态增加大小,并不是在原空间之后的相邻地址增加新空间,因为vector的空间是线性连续分配的,不能保证原空间之后有可供配置的空间。这就解释了上述程序的运行结果。

但是,针对以上自动完成的内存增长过程,由于包括重新分配内存空间、拷贝原空间、释放原空间等步骤,这些过程会降低程序效率,因此可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。下面对上一个程序进行扩展如下,即再定义一个vector b,预先分配10个内存,进行11次push_back操作

#include <iostream>
#include <vector>
using namespace std;
int main()

    vector<int> a;
    cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    for (int i = 0; i < 10; i++)
    
        a.push_back(i);
        cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    
    cout << endl;
    vector<int> b;
    b.reserve(10);
    for (int i = 0; i < 10; i++)
    
        b.push_back(i);
        cout << "b.size(): " << b.size() << "   b.capacity(): " << b.capacity() << endl;
    
    b.push_back(11);
    cout << "b.size(): " << b.size() << "	b.capacity(): " << b.capacity() << endl;
    cout << endl;
    return 0;

运行结果如下:

可以看到,在调用了reserve(10)进行预先内存分配之后,前10次push_back操作vector b 的capacity并没有增长,但是在第11次push_back操作时,由于当前b的size() > capacity(),所以vector自动进行了内存扩充,由10变为20(2倍)。也就是说,前10次能避免每次都进行内存扩充,能有效提高效率,但是第11次就不会。但有一点要注意,如果在当前容量没有用完的情况下,再一次调用reserve(n)函数时, 只有当n > 当前capacity()时,vector当前容量才会改变。再对上述程序进行扩展,分别加上b.reserve(13)和reserve(25),如下

#include <iostream>
#include <vector>
using namespace std;
int main()

    vector<int> a;
    cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    for (int i = 0; i < 10; i++)
    
        a.push_back(i);
        cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    
    cout << endl;
    vector<int> b;
    b.reserve(10);
    for (int i = 0; i < 10; i++)
    
        b.push_back(i);
        cout << "b.size(): " << b.size() << "   b.capacity(): " << b.capacity() << endl;
    
    b.push_back(11);
    cout << "b.size(): " << b.size() << "       b.capacity(): " << b.capacity() << endl;
    cout << endl;


    b.reserve(13);
    cout << "after b.reserve(13):" << endl;
    cout << "b.size(): " << b.size() << "       b.capacity(): " << b.capacity() << endl;

 
	b.reserve(25);
	cout << "after b.reserve(25):" << endl;
	cout << "b.size(): " << b.size() << "       b.capacity(): " << b.capacity() << endl;
	return 0;


运行结果为:

可以看到,在加上b.reserve(13),容器b的capacity并没有由20变为13,但加上 b.reserve(25)时却变化了;这也验证了上述结论。
对于resize() 成员函数只改变元素的数目,不改变vector的容量的说法可以从下面的程序进行验证:

#include <iostream>
#include <vector>
using namespace std;
int main()

    vector<int> a;
    cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    for (int i = 0; i < 10; i++)
    
        a.push_back(i);
        cout << "a.size(): " << a.size() << "   a.capacity(): " << a.capacity() << endl;
    
    cout << endl;
    vector<int> b;
    b.reserve(10);
    for (int i = 0; i < 10; i++)
    
        b.push_back(i);
        cout << "b.size(): " << b.size() << "   b.capacity(): " << b.capacity() << endl;
    
    b.push_back(11);
    cout << "b.size(): " << b.size() << "       b.capacity(): " << b.capacity() << endl;
    cout << endl;

    b.reserve(13);
    cout << "after b.reserve(13):" << endl;
    cout << "b.size(): " << b.size() << "       b.capacity(): " << b.capacity() << endl;
     
    b.resize(5);
    cout << "after b.resize(5):" << endl;
    cout << "b.size(): " << b.size() << "       b.capacity(): " << b.capacity() << endl;
     
    for (int i = 0; i < b.size(); i++)
    
        cout << b[i] << endl;
    
    return 0;


运行结果为:

可以看到,在加上resize(5)之后,容器的capacity()并没有增长,只是size()变为了5,而且通过打印vector元素可知,只截取了前五个元素。

以上是关于C++STL(23) 研究std::vector的内存增长规律的主要内容,如果未能解决你的问题,请参考以下文章

STL 容器和内存泄漏

c ++使用stl向量[关闭]

为啥使用指针的 STL 算法比 std::vector 迭代器快得多?

将 std::vector 与结构一起使用时出现不完整的类型错误

用于 STL 容器的 std::string_view

运行时错误:引用绑定到“std::vector<int, std::allocator<int>>”类型的空指针 (stl_vector.h)