STL容器内存问题

4
我正在使用gcc 4.3.2在linux(Fedora 10和CentOS 5)上实现自己的图形库,并使用STL容器,但是我发现了一些内存问题。 当我构建我的图形时,我使用了大量的内存,足以在top或其他内存使用工具中查看。 我确定我正在释放该内存(我一遍又一遍地审查代码,并使用valgrind检查内存泄漏),但是内存仍在使用中(我可以在topcat /proc/meminfo中查看),并且当我再次创建图形时,它不会增加内存使用率,似乎重复利用分配的内存。

经过数天的调试,我创建了一个非常简单的代码,但它也存在相同的问题。

#include <iostream>
#include <list>

// Object that occupies 128KB.
// Data is not important.
class MyObject
{
public:
    int * a;
    int * b;
    int * c;
    int * d;

    MyObject( )
    {
        a = new int[ 8192 ];
        b = new int[ 8192 ];
        c = new int[ 8192 ];
        d = new int[ 8192 ];
    }

    MyObject( const MyObject & m )
    {
        a = new int[ 8192 ];
        b = new int[ 8192 ];
        c = new int[ 8192 ];
        d = new int[ 8192 ];
    }

    ~MyObject( )
    {
        delete [] a;
        delete [] b;
        delete [] c;
        delete [] d;
    }

    void operator=( const MyObject &m )
    {
        //Do nothing.
    }
};

typedef std::list< MyObject > list_t;

#define MB_TO_ALLOC 1000    // Size in MB that the program must alloc.

#define SLEEP_TIME 5        // Time in seconds that the program must wait until go to another step. 
                        // It's used to give sufficient time for tools update the memory usage

int main( )
{
    std::cout << "Alloc..." << std::endl;

    list_t * list = new list_t( );

    // Number of objects for alloc MB_TO_ALLOC amount of memory
    int nObjects = MB_TO_ALLOC * 1024 / 128;

    for( int i = 0; i < nObjects; ++i )
        list->push_back( MyObject( ) );

    std::cout << SLEEP_TIME << "s to Dealloc..." << std::endl;

    // Wait some time for a tool (like top) to update the memory usage
    sleep( SLEEP_TIME );

    std::cout << "Dealloc..." << std::endl;

    delete list;

    std::cout << SLEEP_TIME << "s to Alloc..." << std::endl;

    // Wait some time for a tool (like top) to update the memory usage
    sleep( SLEEP_TIME );

    //Repeats the procedure for evaluating the reuse of memory
    std::cout << "Alloc..." << std::endl;

    list = new list_t( );

    for( int i = 0; i < nObjects; ++i )
        list->push_back( MyObject( ) );

    std::cout << SLEEP_TIME << "s to Dealloc..." << std::endl;

    sleep( SLEEP_TIME );

    delete list;
}

我曾尝试使用简单的数组或自己编写的列表类,但是在这些情况下,内存会被正常释放。

有人知道发生了什么事吗?如何防止该内存被“保留”?

谢谢!

-- Bruno Caponi


2
请注意,尽管您在构造函数中分配内存并在析构函数中释放它,但您的代码不具备异常安全性,因为其中任何一个分配都可能失败(例如,如果第三个分配失败,则会泄漏对象“a”和“b”)。在这种情况下最好使用智能指针,比如 boost::scoped_ptr(它有一个非常简单的实现)。 - James McNellis
2
@James,我会简单地使用简单的表格:对于这个演示,没有必要动态分配它们。也不需要动态分配“list”。恐怕“Bruno”来自Java/C#背景,才会如此频繁地使用“new”。 - Matthieu M.
2
MyObject 需要一个赋值运算符。否则你会两次删除相同的内存块。 - Derek Ledbetter
top 不是一个好的工具来查找内存使用情况。这将是分配给您的进程的总内存量。此内存由运行时管理,永远不会返回。这可能听起来令人担忧,但这并不是问题,因为已使用的内存将被分页出去,因此这并不等同于物理内存。 - Martin York
@Matthieu: 哦,对了。傻了我。 - James McNellis
显示剩余5条评论
6个回答

4

gcc的STL有自己的内存管理层,它会获取大块内存并不释放;你可以设置一个环境变量让它使用原始的new调用。

GLIBCPP_FORCE_NEW=1

我猜这也使它免费了。这个环境变量通常在使用valgrind时使用,以便valgrind不认为有东西泄漏。


1
备注:环境变量的名称曾经发生过几次更改。谷歌会告诉您哪个名称由哪个GCC版本使用。 - pm100
我在谷歌上找到了环境变量是GLIBCXX_FORCE_NEW,但我已经尝试过两种方式,但内存仍未释放。你使用Linux吗?你可以在你的机器上测试一下吗? - Bruno Caponi
是 Linux。不,我不会测试(抱歉)。 - pm100

4

除了STL容器外,libc本身也可能会这样做(实现new/delete-malloc/free)。用户空间库可以自由地保留内存以备后用。分配/释放是一项昂贵的操作(从时钟周期的角度来看),因此许多实现尝试避免这种情况。


2
一个容器类的内存使用是由容器的分配器处理的(这个分配器作为构造函数参数传递给std::list,默认情况下是std::allocator)。默认分配器的实现可能选择不立即将内存返回到系统,以防止堆的过度碎片化。
如果您想更直接地控制这个过程,可能需要实现一个自定义的分配器。

我调试了默认分配器,显然内存应该正常返回给系统。然后我使用类似于标准分配器(::operator new()和放置new)的策略实现了自己的分配器和自己的列表,并且问题没有出现。 - Bruno Caponi

0
我不能确定会发生什么,但这个问题(如果是问题的话)是可以重现的。
我原以为这是一个内存池问题,因为STL在使用特定分配器时会这么做。 然而,当我深入研究列表<>时,我发现只有一个“new_allocator”,它仅仅返回全局new操作符的结果,而已。
pointer
 allocate(size_type __n, const void* = 0)
 { return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp))); }

在我看来,这种行为是由于 glibc 或 stdlibc++ 的内存处理机制引起的。 在我快速查看后,我无法找出如何规避这种行为而不实现自定义分配器 也不知道 自定义分配器是否一定会表现出不同的行为。

我尝试了另一种没有使用 STL 的测试方式,然后我发现资源上下波动。 我建议创建一个自定义分配器,用放置 new 在数组中分配任意数量的元素,并负责分配/释放这些数组。 逻辑告诉我,资源使用必须像“非 STL”测试一样表现。

请尝试并告诉我们发生了什么。我不会亲自去做,因为我现在没有时间,尽管我很好奇;)

注:在这里,“大三件事”规则没有影响。据我所知,没有内存泄漏 并且 对象的内容不相关。Bruno 可以进行数据复制、自赋值检查等操作,但只是使用空的复制构造函数来说明其观点。


0
我遇到了同样的问题,经过漫长的调试,我编写了一个示例程序,说明这是一些内核(或者可能是g ++)的问题。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctime>

static const size_t CHUNKS_COUNT = 1024 * 1024;
void* chunks[CHUNKS_COUNT];

int main(int argc, const char *argv[]) {
  bool additionalAlloc = false;
  if (argc > 1)
    additionalAlloc = true;

  fprintf(stdout, "%lu: starting allocating chunks, additionalAlloc=%d\n",
    time(NULL), additionalAlloc);

  for (size_t n = 0; n < 2; ++n) {
    void* additionalChunk;

    // 1GB
    for (size_t c = 0; c < CHUNKS_COUNT; ++c) {
      static const size_t BASE_CHUNK_SIZE = 1024;
      chunks[c] = malloc(BASE_CHUNK_SIZE);
    }

    if (additionalAlloc) {
      // 33 is arbitrary, but for instance for 123 given example
      // is not working - magic :-)
      additionalChunk = malloc(33);
    }

    fprintf(stdout, "%lu: finished allocating chunks, n=%lu\n",
      time(NULL), n);
    sleep(60);

    for (size_t c = 0; c < CHUNKS_COUNT; ++c) {
      free(chunks[c]);
    }

    if (additionalAlloc)
      free(additionalChunk);

    fprintf(stdout, "%lu: finished freeing chunks, n=%lu\n",
      time(NULL), n);
    sleep(60);
  }

  sleep(60);

  fprintf(stdout, "%lu: finishing program\n", time(NULL));

  return 0;
}

当不带参数运行时(additionalAlloc为false),在调用free后,内存将被释放到系统中。但是当带参数运行时(additionalAlloc为true),内存仅在程序完成后才被释放。我在Debian Squeeze上使用4.4.5-1 g++和2.6.18-6-xen-amd64内核运行它。我不知道它在其他系统上如何工作,但是看到附加块的123字节会导致不同的程序行为,有很大的可能性它不会工作 - 但请相信我,对于这些设置,它可以正常工作 :-)

PS. 有人能解释为什么附加块的33和123值会导致不同的行为吗?


0

在Linux中,用户内存是通过BRK系统调用从内核分配到进程中的,该调用向下扩展数据指针,使得更多的RAM可用于进程。这是内核向进程传递普通内存的唯一方式。我们也可以使用mmap来获取内存,这允许进程指定起始地址(而不是数据指针),但没有分配器会这样做,因为这大大增加了分配器和内核分配器必须做的工作量。因此,分配给用户进程的内存可以很容易地被内核回收,直到进程终止。如果您的特定应用程序正在进行许多大型分配/释放操作,则对于这些分配,使用mmap可能是将内存映像大小保持在较低水平的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接