【问题标题】:vector of list memory leak in C++ on OSX 10.8OSX 10.8 上 C++ 中的列表内存泄漏向量
【发布时间】:2013-06-19 13:41:45
【问题描述】:

我在实现向量> 时遇到了内存问题。代码如下:

struct structure{
    vector< list<size_t>  > Ar;

    structure(int n){
        Ar.reserve(n);
        for(size_t i =0; (int) i<  n;i++){
            list<size_t> L;
            Ar.push_back(L);
        }
    }

    ~structure(){
       vector<list<size_t> >::iterator it = Ar.begin();
       while(it < Ar.end()){
           (*it).clear();
           ++it;
       }
       Ar.clear();
    }


};

int main() {

    for(size_t k = 0; k <100 ; k++){    
        structure test_s = structure(1000*(100 - k));
    }
    return 0;
}

随着时间的推移,分配给该程序的物理内存应该会减少,因为通过在构造函数中使用 100 - k 分配给 test_s 的内存越来越少。这不是我观察的!相反,物理内存在运行的一半左右增加!

由于我在一个消耗大量内存的更大程序中使用此代码,这有点灾难!

有两个细节我觉得很奇怪:

首先,物理内存使用量并没有逐渐增加,即使对象的大小在 for 循环的每个阶段都在变化,而是在 for 循环的第 50 次迭代左右内存突然增加。这种情况在我每次跑步时都会发生(而且我已经做过很多次了!)。内存增加的迭代不是随机的!

其次,当我将静态 size_t(例如 10000)传递给 structure(size_t) 构造函数时,我不再遇到问题了。您可能已经猜到,静态值对我的代码不是很有用,因为我需要能够动态分配结构对象的大小。

我在 macOS 10.8.3 上使用 g++ 进行编译。我没有尝试在另一个平台上编译,因为我更愿意继续在我的 Mac 上工作。

我尝试过的所有内存管理工具(Apple Instruments 和 Valgrind)都不是特别有用。 Valgrind 只返回对库的引用,而不是对程序本身的引用。

任何帮助将不胜感激!

干杯, 普拉门

【问题讨论】:

  • 你的析构函数什么都不做,你的构造函数可以写成structure(int n) : Ar(n) {}。这里没有内存泄漏。
  • 零规则。你的类不需要析构函数。我什至懒得去检查它是否是原因。
  • 您是如何发现您的程序存在内存泄漏的?您知道您的程序不必(立即)将内存返回给操作系统吗?
  • 我在活动监视器中观察到内存增加。这是错的吗?感谢有关构造函数和析构函数的提示。我很确定这些不是原因;如果我将文字传递给构造函数,则不会出现问题。

标签: c++ list memory vector stl


【解决方案1】:

C++ 分配器在使用完内存后不一定会将内存返回给操作系统,但通常会保留它,因为您可能很快就会需要它。

我不熟悉 OS X 分配器的详细信息,但分配器从操作系统中获取较大块的内存然后将它们视为单独的池是很常见的。
这可能就是您所看到的,随着第一块内存被填满,突然增长。
也有可能您在“较大”分配和“较小”分配之间通过了某个阈值,而您只是看到为稍微较小的东西增加了一个池 - 一些分配器会这样做。
当然,也有可能是完全不同的原因。

当您为每个块使用相同大小时的差异很可能是因为分配器很容易使用最近释放的相同大小的块来填充请求。

当块的大小不同时,分配不同大小的新块比将空闲块分成两个较小的块更快。
(不幸的是,这也可能导致内存碎片。如果你得到许多分散的小块,可能会发生尽管总共有足够的空间却无法满足大的分配请求。)

总而言之:内存分配器和操作系统现在相当复杂,您不能看到内存分配的增长就肯定会说内存泄漏。
在你的情况下,我会相信 valgrind 和 Instruments。

【讨论】:

  • 谢谢!我会尽量减少使用不同大小的分配。在我看来,列表通常应该呈现这种内存不确定性:由于可以动态地将对象添加和删除到列表中,分配器通常不会对正在使用的内存量感到困惑吗?对不起,我是这个网站的新手:我应该打开一个新线程来问这个吗?
【解决方案2】:

我没有看到该代码中有任何泄漏,但有很多不需要的代码。简化的代码是:

struct structure{
    vector< list<size_t>  > Ar;

    structure(int n): Ar(n) // initialize the vector with n empty lists
    {
    }

    // destructor unneeded, since Ar will be destroyed anyway, with all of its elements.
};

但这并不能回答你的问题。

堆内存分配并不意味着物理内存分配。现代操作系统使用虚拟内存,通常由分页文件支持。堆分配从虚拟内存中获取内存,操作系统决定是否需要更多或更少的物理内存。将内存释放到虚拟内存并不意味着释放物理内存(如果其他进程不需要,为什么要在那个时候这样做?)。

此外,堆内存分配不直接转换为虚拟内存分配。通常,虚拟内存分配具有大粒度,因此不适合小分配。然后,堆管理器分配虚拟内存块并管理所有堆分配。 (如果虚拟内存不够,堆管理器会要求更多)。何时释放未使用的虚拟内存块取决于堆管理器的实现方式。

要使事情变得更复杂一些,分配和释放不同大小的内存会产生堆碎片,具体取决于分配/释放模式以及堆的实现方式。

物理内存不是此类程序中内存泄漏的良好指标。会是更好的私有(虚拟)内存或类似的内存。

【讨论】:

  • 谢谢!我将更改我正在查看的内存值。有没有办法在程序运行时帮助分配器处理碎片化内存?我也要把浪费的代码去掉!
  • 我已经更改了代码以避免及时分配。现在,我创建一个结构对象,为整个 for 循环的向量分配足够的内存。我没有使用析构函数来清除内存,而是添加了一个函数来清除向量中列表的内容。这对于我的程序的内存管理来说已经足够了,因为列表的内容比容器本身大得多,而且内存分配不再有跳跃!感谢大家的贡献!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-01
  • 2012-08-07
  • 1970-01-01
  • 2016-01-21
  • 2017-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多