备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 技术最杂谈 map容器clear操作不会释放内存?
9 0

海报分享

原创

map容器clear操作不会释放内存?

一,map容器clear操作不会释放内存?

当第一次听到这个说法的时候确实有点惊讶。一直记得map容器底层红黑树会自动析构节点,并释放内存。在同事进行了代码验证,并百度了答案后,我也变得不确定起来了。

只有再次看了一遍《STL源码剖析》。希望最终能够回答这个问题。

最后发现这个问题即对又不对。为什么呢?看后面的分析。

二,map的clear操作

2.1 clear源码

Void clear(){t.clear();}; //P241页《STL源码剖析》

由于关联容器底层是红黑树实现,所以map的clear也是调用的红黑树的clear函数。

那么我们接下来再看看红黑树的clear操作。

在开发机中可以找到文件stl_tree.h,erase操作的源码如下:

该操作中核心调用的destroy_node分成两步:

1,先析构元素,2,然后释放内存。(destroy_node操作与《STL源码剖析》书中的写法有一点出入,不过本质是一样的)

其中步骤2释放内存操作代码如下:

这里的deallocate操作就是STL中封装的全局内存分配操作函数。

读到这里就比较清楚了,map容器的erase以及clear操作底层都是调用的全局函数deallocate进行内存释放操作。

2.2 deallocate源码分析

Std::alloc为内存配置器,具体又分为一级和二级配置器。负责为容器提供内存空间操作,提供了常用的两个操作:allocate分配内存,deallocate释放内存。

一级配置器与二级配置器的区别:

如果申请的内存块大于128字节,则STL使用一级配置器。Allocate底层调用malloc操作,deallocate调用free操作。

如果申请的内存块小于128字节,则使用二级配置器。该配置器具体实现类似于一个内存池。减少频繁系统,提高内存分配效率。缺点:为了维护小块内存,需要额外的空间来进行管理。

2.3 小结

到这里就可以有一个小结论。

1,当map中的元素占用内存大小总和小于128字节时,则erase或者clear操作确实不会释放内存(包括虚拟和物理内存)。

2,当元素对象大于或等于128字节,则直接系统调用malloc或者free进行内存的分配(malloc只是分配虚拟内存)

3,只有当对分配的内存区域初始化时,操作系统才会分配物理内存,产生minflt小缺页。

三,STL中常用容器内存操作总结

3.1 vector容器

3.1.1 空容器

容量大小为0。

vector<int> v;

cout<<"size: "<<v.size()<<", capacity: "<<v.capacity()<<endl;//0 0

3.1.2 写入操作(push_back等)

容量变化:

如果是对容量为0的容器进行一次push_back操作,则容器大小变为1。

如果该容器容量已满,则会对容器容量扩容一倍,并把旧容器的元素拷贝至新内存中。

元素构造:

如果容器没有容量,则分两步完成操作:先allocate分配内存,然后construct构造元素。

如果有足够容量,则只调用construct构造元素即可。

3.1.3删除操作(pop_back,erase,clear等)

只调用析构函数destroy,并不会进行内存的释放。即容器的capacity并不会变化。

3.1.4 析构函数

可以看到,即调用了析构函数,也调用了内存释放函数。

所以常常vector或者string在进行一系列操作后,容量变得非常大,那么可以通过下面的技巧进行容量的缩减。

vector<int> v;

v.push_back(1);

v.push_back(2);

……

vector<int>().swap(v); // 当clear()用。和临时对象交换,交换后临时对象消失。真正将v释放掉。

// or: v.swap(vector<int>());

3.2 list操作

3.2.1 empty操作

Empty操作性能好于size()==0。《Effective STL》

3.2.2 写入操作(insert)

两步完成:1,分配内存。2,构造node,并进行赋值。

3.2.3 删除操作(pop_back,erase等)

分别调用析构函数,内存释放函数完成操作。

3.3 deque双端队列

分段连续,随机迭代器。没有容量的概念

3.3.1 初始状态

至少存在一个缓冲区。

3.3.2 写入操作

如果分段缓冲区还有空间,则直接调用constuct操作。否则分配内存,分段缓冲区,然后构造该元素。

3.3.3 删除操作

如果删除的一段缓冲区还有数据,则只析构对象,并不释放内存。

如果删除后,该段缓冲区没有数据,则析构元素,并释放内存。

Clear或者erase操作后,会保留一个缓存区,只对其析构元素。其他缓冲区即析构函数,也释放内存。

3.4 关联容器之map

关联容器都是红黑树(hash_xx除外)。具有较高的查找和插入效率,元素有序。

3.4.1 写入操作

Insert操作会先分配内存,初始化树node节点,node节点元素类型为pair。

3.4.2 下标操作

Map容器的下标操作既可以作为左值,也可以作为右值。因为其返回的是引用。

也是一个效率非常低的函数, 尽量少用。

另外一个副作用是:当key值在map中不存在时,会自动在map中插入一个key,value为默认值的元素。造成map结构增大。

如下代码:

map<int, int> map_int; cout<<"size "<<map_int.size()<<endl; //0