vector 内存泄漏问题?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vector 内存泄漏问题?相关的知识,希望对你有一定的参考价值。

如题。
我在一个普通C++函数中有以下处理。

char* adrStrHead[] = "1C51","2C51" ;

for (int i = 0; i < 2; i++)
_cVector.push_back(adrStrHead[i]);


其中_cVector 是类的成员变量,类型是Vector<char*> 的。

问题:
1._cVector中存储的究竟是指针,还是指针指向的字符串?访问时得到的结果是字符串。
2。当这个类生命周期结束,调用完析购函数后,这个_cVector如何释放空间?会不会内存泄漏?为什么?

C++内置的数组支持容器的机制,但是它不支持容器抽象的语义。要解决此问题我们自己实现这样的类。在标准C++中,用容器向量(vector)实现。容器向量也是一个类模板。
标准库vector类型使用需要的头文件:#include <vector>。vector 是一个类模板。不是一种数据类型,vector<int>是一种数据类型。Vector的存储空间是连续的,list不是连续存储的。

一、 定义和初始化
vector< typeName > v1; //默认v1为空,故下面的赋值是错误的v1[0]=5;
vector<typeName>v2(v1); 或v2=v1;或vector<typeName> v2(v1.begin(), v1.end());//v2是v1的一个副本,若v1.size()>v2.size()则赋值后v2.size()被扩充为v1.size()。
vector< typeName > v3(n,i);//v3包含n个值为i的typeName类型元素
vector< typeName > v4(n); //v4含有n个值为0的元素
int a[4]=0,1,2,3,3; vector<int> v5(a,a+5);//v5的size为5,v5被初始化为a的5个值。后一个指针要指向将被拷贝的末元素的下一位置。
vector<int> v6(v5);//v6是v5的拷贝
vector< 类型 > 标识符(最大容量,初始所有值);

二、 值初始化
1> 如果没有指定元素初始化式,标准库自行提供一个初始化值进行值初始化。
2> 如果保存的式含有构造函数的类类型的元素,标准库使用该类型的构造函数初始化。
3> 如果保存的式没有构造函数的类类型的元素,标准库产生一个带初始值的对象,使用这个对象进行值初始化。
三、vector对象最重要的几种操作
1. v.push_back(t) 在容器的最后添加一个值为t的数据,容器的size变大。
另外list有push_front()函数,在前端插入,后面的元素下标依次增大。
2. v.size() 返回容器中数据的个数,size返回相应vector类定义的size_type的值。v.resize(2*v.size)或

v.resize(2*v.size, 99) 将v的容量翻倍(并把新元素的值初始化为99)
3. v.empty() 判断vector是否为空
4. v[n] 返回v中位置为n的元素
5. v.insert(pointer,number, content) 向v中pointer指向的位置插入number个content的内容。
还有v. insert(pointer, content),v.insert(pointer,a[2],a[4])将a[2]到a[4]三个元素插入。
6. v.pop_back() 删除容器的末元素,并不返回该元素。
7.v.erase(pointer1,pointer2) 删除pointer1到pointer2中间(包括pointer1所指)的元素。
vector中删除一个元素后,此位置以后的元素都需要往前移动一个位置,虽然当前迭代器位置没有自动加1,
但是由于后续元素的顺次前移,也就相当于迭代器的自动指向下一个位置一样。
8. v1==v2 判断v1与v2是否相等。
9. !=、<、<=、>、>= 保持这些操作符惯有含义。
10. vector<typeName>::iterator p=v1.begin( ); p初始值指向v1的第一个元素。*p取所指向元素的值。
对于const vector<typeName>只能用vector<typeName>::const_iterator类型的指针访问。
11. p=v1.end( ); p指向v1的最后一个元素的下一位置。
12.v.clear() 删除容器中的所有元素。12.v.clear() 删除容器中的所有元素。

#include<algorithm>中的泛函算法
搜索算法:find() 、search() 、count() 、find_if() 、search_if() 、count_if()
分类排序:sort() 、merge()
删除算法:unique() 、remove()
生成和变异:generate() 、fill() 、transformation() 、copy()
关系算法:equal() 、min() 、max()
sort(v1.begin(),vi.begin()+v1.size/2); 对v1的前半段元素排序
list<char>::iterator pMiddle =find(cList.begin(),cList.end(),'A');找到则返回被查内容第一次出现处指针,否则返回end()。
vector< typeName >::size_type x ; vector< typeName >类型的计数,可用于循环如同for(int i)

初学C++的程序员可能会认为vector的下标操作可以添加元素,其实不然:

vector<int> ivec; // empty vector

for (vector<int>::size_type ix = 0; ix != 10; ++ix)

ivec[ix] = ix; // disaster: ivec has no elements

上述程序试图在ivec中插入10个新元素,元素值依次为0到9的整数。但是,这里ivec是空的vector对象,而且下标只能用于获取已存在的元素。

这个循环的正确写法应该是:

for (vector<int>::size_type ix = 0; ix != 10; ++ix)

ivec.push_back(ix); // ok: adds new element with value ix

警告:必须是已存在的元素才能用下标操作符进行索引。通过下标操作进行赋值时,不会添加任何元素。仅能对确知已存在的元素进行下标操作

四、内存管理与效率

1.使用reserve()函数提前设定容量大小,避免多次容量扩充操作导致效率低下。

关于STL容器,最令人称赞的特性之一就是是只要不超过它们的最大大小,它们就可以自动增长到足以容纳你放进去的数据。(要知道这个最大值,只要调用名叫max_size的成员函数。)对于vector和string,如果需要更多空间,就以类似realloc的思想来增长大小。vector容器支持随机访问,因此为了提高效率,它内部使用动态数组的方式实现的。在通过 reserve() 来申请特定大小的时候总是按指数边界来增大其内部缓冲区。当进行insert或push_back等增加元素的操作时,如果此时动态数组的内存不够用,就要动态的重新分配当前大小的1.5~2倍的新内存区,再把原数组的内容复制过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。正如上面的代码告诉你的那样。而进行pop_back操作时,capacity并不会因为vector容器里的元素减少而有所下降,还会维持操作之前的大小。对于vector容器来说,如果有大量的数据需要进行push_back,应当使用reserve()函数提前设定其容量大小,否则会出现许多次容量扩充操作,导致效率低下。

reserve成员函数允许你最小化必须进行的重新分配的次数,因而可以避免真分配的开销和迭代器/指针/引用失效。但在我解释reserve为什么可以那么做之前,让我简要介绍有时候令人困惑的四个相关成员函数。在标准容器中,只有vector和string提供了所有这些函数。

(1) size()告诉你容器中有多少元素。它没有告诉你容器为它容纳的元素分配了多少内存。
(2) capacity()告诉你容器在它已经分配的内存中可以容纳多少元素。那是容器在那块内存中总共可以容纳多少元素,而不是还可以容纳多少元素。如果你想知道一个vector或string中有多少没有被占用的内存,你必须从capacity()中减去size()。如果size和capacity返回同样的值,容器中就没有剩余空间了,而下一次插入(通过insert或push_back等)会引发上面的重新分配步骤。
(3) resize(Container::size_type n)强制把容器改为容纳n个元素。调用resize之后,size将会返回n。如果n小于当前大小,容器尾部的元素会被销毁。如果n大于当前大小,新默认构造的元素会添加到容器尾部。如果n大于当前容量,在元素加入之前会发生重新分配。
(4) reserve(Container::size_type n)强制容器把它的容量改为至少n,提供的n不小于当前大小。这一般强迫进行一次重新分配,因为容量需要增加。(如果n小于当前容量,vector忽略它,这个调用什么都不做,string可能把它的容量减少为size()和n中大的数,但string的大小没有改变。在我的经验中,使用reserve来从一个string中修整多余容量一般不如使用“交换技巧”,那是条款17的主题。)

这个简介表示了只要有元素需要插入而且容器的容量不足时就会发生重新分配(包括它们维护的原始内存分配和回收,对象的拷贝和析构和迭代器、指针和引用的失效)。所以,避免重新分配的关键是使用reserve尽快把容器的容量设置为足够大,最好在容器被构造之后立刻进行。

例如,假定你想建立一个容纳1-1000值的vector<int>。没有使用reserve,你可以像这样来做:

vector<int> v;
for (int i = 1; i <= 1000; ++i) v.push_back(i);
在大多数STL实现中,这段代码在循环过程中将会导致2到10次重新分配。(10这个数没什么奇怪的。记住vector在重新分配发生时一般把容量翻倍,而1000约等于210。)

把代码改为使用reserve,我们得到这个:

vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);
这在循环中不会发生重新分配。

在大小和容量之间的关系让我们可以预言什么时候插入将引起vector或string执行重新分配,而且,可以预言什么时候插入会使指向容器中的迭代器、指针和引用失效。例如,给出这段代码,

string s;
...
if (s.size() < s.capacity())
s.push_back('x');

push_back的调用不会使指向这个string中的迭代器、指针或引用失效,因为string的容量保证大于它的大小。如果不是执行push_back,代码在string的任意位置进行一个insert,我们仍然可以保证在插入期间没有发生重新分配,但是,与伴随string插入时迭代器失效的一般规则一致,所有从插入位置到string结尾的迭代器/指针/引用将失效。

回到本条款的主旨,通常有两情况使用reserve来避免不必要的重新分配。第一个可用的情况是当你确切或者大约知道有多少元素将最后出现在容器中。那样的话,就像上面的vector代码,你只是提前reserve适当数量的空间。第二种情况是保留你可能需要的最大的空间,然后,一旦你添加完全部数据,修整掉任何多余的容量。

2.使用“交换技巧”来修整vector过剩空间/内存

有一种方法来把它从曾经最大的容量减少到它现在需要的容量。这样减少容量的方法常常被称为“收缩到合适(shrink to fit)”。该方法只需一条语句:vector<int>(ivec).swap(ivec);
表达式vector<int>(ivec)建立一个临时vector,它是ivec的一份拷贝:vector的拷贝构造函数做了这个工作。但是,vector的拷贝构造函数只分配拷贝的元素需要的内存,所以这个临时vector没有多余的容量。然后我们让临时vector和ivec交换数据,这时我们完成了,ivec只有临时变量的修整过的容量,而这个临时变量则持有了曾经在ivec中的没用到的过剩容量。在这里(这个语句结尾),临时vector被销毁,因此释放了以前ivec使用的内存,收缩到合适。

3.用swap方法强行释放STL Vector所占内存

template < class T> void ClearVector( vector<T>& v )

vector<T>vtTemp;
vtTemp.swap( v );


vector<int> v ;
nums.push_back(1);
nums.push_back(3);
nums.push_back(2);
nums.push_back(4);
vector<int>().swap(v);

/* 或者v.swap(vector<int>()); */

/*或者 std::vector<int> tmp = v; v.swap(tmp); ; //加大括号 是让tmp退出 时自动析构*/

五、Vector 内存管理成员函数的行为测试

C++ STL的vector使用非常广泛,但是对其内存的管理模型一直有多种猜测,下面用实例代码测试来了解其内存管理方式,测试代码如下:

#include <iostream>
#include <vector>
using namespace std;

int main()

vector<int> iVec;
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //1个元素, 容器容量为1

iVec.push_back(1);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //2个元素, 容器容量为2

iVec.push_back(2);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //3个元素, 容器容量为4

iVec.push_back(3);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //4个元素, 容器容量为4

iVec.push_back(4);
iVec.push_back(5);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //5个元素, 容器容量为8

iVec.push_back(6);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //6个元素, 容器容量为8

iVec.push_back(7);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //7个元素, 容器容量为8

iVec.push_back(8);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //8个元素, 容器容量为8

iVec.push_back(9);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //9个元素, 容器容量为16
/* vs2005/8 容量增长不是翻倍的,如
9个元素 容量9
10个元素 容量13 */

/* 测试effective stl中的特殊的交换 swap() */
cout << "当前vector 的大小为: " << iVec.size() << endl;
cout << "当前vector 的容量为: " << iVec.capacity() << endl;
vector<int>(iVec).swap(iVec);

cout << "临时的vector<int>对象 的大小为: " << (vector<int>(iVec)).size() << endl;
cout << "临时的vector<int>对象 的容量为: " << (vector<int>(iVec)).capacity() << endl;
cout << "交换后,当前vector 的大小为: " << iVec.size() << endl;
cout << "交换后,当前vector 的容量为: " << iVec.capacity() << endl;

return 0;


六、vector的其他成员函数

c.assign(beg,end)
将[beg; end)区间中的数据赋值给c。
c.assign(n,elem)
将n个elem的拷贝赋值给c。
c.at(idx)
传回索引idx所指的数据,如果idx越界,抛出out_of_range。
c.back()
传回最后一个数据,不检查这个数据是否存在。
c.front()
传回地一个数据。
get_allocator
使用构造函数返回一个拷贝。
c.rbegin()
传回一个逆向队列的第一个数据。
c.rend()
传回一个逆向队列的最后一个数据的下一个位置。
c.~ vector <Elem>()
销毁所有数据,释放内存。
参考技术A char*的当然是保存指针了
如果不是new或malloc出来的,是不需要释放空间的
如果是,则析购函数中应该取出所有的指针进行释放本回答被提问者采纳

FastMM 报告 C++ Builder 6 中 STL 容器的内存泄漏

【中文标题】FastMM 报告 C++ Builder 6 中 STL 容器的内存泄漏【英文标题】:FastMM reports memory leaks on STL containers in C++ Builder 6 【发布时间】:2016-07-05 11:15:08 【问题描述】:

当我创建一个空的控制台应用程序并在其中使用 STL 容器时,FastMM 在应用程序关闭时报告内存泄漏。

例如,如果我在main() 中创建std::vector&lt;int&gt;

std::vector<int> v;

编译,运行,关闭,不报告泄漏。

如果我这样做:

std::vector<int> v;
v.push_back(100);

我明白了:

此应用程序已泄漏内存。小块泄漏是:

309 - 340 字节:未知 x 1

同样,我收到了关于以下内容的泄漏报告:

std::vector<int> v;
v.push_back(100);
v.clear();

还报告了泄漏:

std::vector<int> v;
v.reserve(1);

对于某些容器,例如std::deque,只需创建一个容器就足够了,即使不更改其内容,应用程序关闭时也会报告泄漏。

谁能解释发生了什么?我使用 Borland C++Builder 6 和 FastMM4。我一直在更改 FastMMOptions.inc 中的各种设置,但我仍然看到报告了这些泄漏。

【问题讨论】:

Borland C++Builder 6 似乎是在 2002 年问世的......你有机会使用更现代的编译器吗?甚至是更新版本的 C++Builder?对于软件而言,14 年是一段很长的时间。 我说,因为给定代码的 sn-p,除了编译器错误的 std::vector 实现之外,没有什么会导致内存泄漏(没有看到周围的上下文)。或检漏仪报告的误报。 是的,我们有 Borland XE7,新项目是在新环境中开发的。但是我需要调查现有项目中的内存泄漏,由于它们的大小,我们还没有迁移到 XE7。我发现 FastMM 非常有用,但是因为在我们的代码中很多地方都使用了 STL,所以它指出了让我非常困惑的漏洞。 @CoryKramer:有时人们无法选择使用现代编译器。我自己在日常工作中仍然使用 C++Builder 6。不是因为我想要,而是因为我的公司需要它(不是因为多年来没有尝试升级,PTB 不允许它,因为新版本更不稳定并且不能满足业务需求)。 std::deque 在 C++Builder 6 中被窃听且无法使用 【参考方案1】:

清除std::vector 不会释放用于vector 内部数组的内存,它只是破坏数组中的项目,然后将std::vector::size() 设置为0。数组本身仍然是分配的,所以它可以重复用于vector 中推送的新项目。 vector 的析构函数将释放数组。

在 C++Builder 6 中,默认的 STL 库是 STLPort(在 C++Builder 2006 中被 Dinkumware 取代)。 STLPort 对~std::vector() 的实现仅破坏了数组项(好像clear() 已被调用),但不会释放数组本身,因此您看到的是“泄漏”。根据 STLPort 网站上的以下常见问题解答,这实际上根本不是泄漏:

Q6.2 My tool detect memory leaks in application with STLport. Is this leak from STLport?

A6.2 在大多数情况下,这是某些工具错误支持的“伪内存泄漏”。

在STLport的默认编译中,节点分配器用于分配内部内存。节点分配器通过预先分配一大块内存并分发小内存块来工作。在运行使用 STLport 的应用程序期间,内存块没有被释放(即它没有返回到系统,有像 BoundsChecker、Purify 或 Valgrind 这样的工具来检查 memorytml 泄漏,对于在不再使用。当使用 STLport 的节点分配器时,这些工具可能会报告错误的内存泄漏。内存块通常在应用程序端释放,但内存检查器通常会在此之前报告内存泄漏。另一个内存问题当您使用内部使用 STLport 并静态链接到它的共享库(例如 DLL,此问题特定于 Windows DLL 模型)时可能会报告。如果内存在 dll 中分配并在另一个 dll 中释放,则 STLport 节点分配器将保留释放的内存以备将来使用。如果您不使用此内存,那么即使没有真正的内存泄漏,您的应用程序全局内存消耗也会增长直到应用程序崩溃。这就是为什么您应该始终使用连贯的配置 dll 中的所有内容或静态库中的所有内容。

有一些方法可以消除伪内存泄漏(由于内存在程序结束时被正确释放,泄漏只是伪内存)。您可以使用 STLport 中使用的另一个分配器。打开文件"stlport/stl/_site_config.h" 并取消注释以下任一选项:

_STLP_USE_NEWALLOC   enables a simple allocator that uses "new/delete"
_STLP_USE_MALLOC     enables a simple allocator that uses "malloc/free"

new/delete 分配器具有提供跟踪内存不足的入口点的优势,有关详细信息,请参阅编译器文档中的 set_new_handler 或 C++ 标准。

您也可以定义以下符号,只需在 "stlport/stl/_site_config.h" 中取消注释即可。

_STLP_LEAKS_PEDANTIC

该符号强制释放所有内存块。另请参阅配置文件中符号周围的 cmets。

请注意,如果您对文件进行任何更改,您必须重新编译 STLport 以及您的应用程序和所有依赖库!

还有一些定义有助于调试 STLport 中的内存问题。在 _STLP_DEBUG 模式下,只需在“./stlport/stl_user_config.h”或您的项目设置中定义以下符号:

_STLP_DEBUG_ALLOC
_STLP_DEBUG_UNINITIALIZED        

您不需要为这些选项重新构建 STLport,但如果您对文件进行任何更改,则必须重新构建您的应用程序和所有依赖库。

也就是说,在早期 C++Builder 版本中使用的 RogueWave STL 也随 C++Builder 6 一起提供,以向后兼容旧代码,并且不会遇到此问题。您可以通过在项目的条件列表中定义 _USE_OLD_RW_STL 从 STLPort 切换到 RogueWave。

【讨论】:

优秀且内容丰富的回复。定义 _USE_OLD_RW_STL 后,FastMM 不会报告我们项目中使用的容器的任何泄漏(std::vectorstd::liststd::deque)。非常感谢你。现在查看内存泄漏报告要容易得多。 我注意到的另一件事是,std::cout 的任何使用都会报告泄漏。幸运的是,我们的代码中没有很多,但如果你知道另一个“神奇的条件定义”来阻止那些出现在报告中,那就太好了。示例:std::cout &lt;&lt; "hello" &lt;&lt; endl; 报告:13 - 20 字节:未知 x 1 21 - 36 字节:std::codecvt x 1,未知 x 2 37 - 52 字节:未知 x 2 53 - 68 字节:std::ctype x 1,__rwstd::locale_imp x 2 85 - 100 字节:未知 x 2 485 - 532 字节:未知 x 3,而 printf("hello\n"); 报告没有泄漏

以上是关于vector 内存泄漏问题?的主要内容,如果未能解决你的问题,请参考以下文章

如何保证应用程序不包含内存泄漏?

gulp & nodemon 导致的内存泄漏

将指针推回指针向量是不是存在内存泄漏?

iPhone - 内存泄漏 - NSData dataWithContentsOfUrl & UIWebView

ThreadLocal 和内存泄漏

MPI内存泄漏