STL容器内存问题

时间:2010-10-01 18:33:58

标签: c++ linux memory gcc stl

我正在使用gcc 4.3.2在linux(Fedora 10和CentOS 5)中实现我自己的图形库并使用STL容器然后我发现了一些内存问题。当我构建我的图形时,我会使用大量内存来在 top 或其他内存使用工具中查看。我确定我正在释放那个内存(我一次又一次地查看了代码,并使用valgrind来检查内存泄漏),但是内存仍在使用中(我可以在 top 中查看或 cat / 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;
}

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

有谁知道发生了什么事?如何防止这个内存被“保留”?

谢谢!

- 布鲁诺卡波尼

6 个答案:

答案 0 :(得分:4)

gcc STL有自己的内存管理层,它可以抓取大块内存并且不会回复它们;你可以设置一个env变量来使它使用原始的新调用

GLIBCPP_FORCE_NEW=1

我认为这也让它自由了。这个env var通常在使用valgrind时使用,这样valgrind就不会认为事情泄露了

答案 1 :(得分:3)

除了STL容器之外,它可能是libc本身这样做的(new / delete - malloc / free实现)。用户空间库可以自由地保留内存以供以后重用。分配/解除分配是一项昂贵的操作(就时钟周期而言),因此许多实现都试图避免这种情况。

答案 2 :(得分:2)

容器类的内存使用由容器的allocator处理(它作为构造函数参数传递给std::list,默认情况下为std::allocator。默认分配器的实现可以选择不立即将内存返回给系统,以防止堆的过多碎片。

如果您想要更直接地控制它,您可能必须实现自定义分配器。

答案 3 :(得分:0)

在Linux上,用户内存通过BRK系统调用从内核分配给进程,该进程向下扩展数据指针,使进程可以使用更多内存。这是内核将正常内存传递给进程的唯一方法。也可以通过使用mmap来获取内存,mmap允许进程指定起始地址(除数据指针之外)但没有分配器执行此操作,因为它大大增加了分配器和内核分配器必须的工作量做。出于这个原因,内核可以很容易地回收给用户进程的内存,直到进程终止。如果您的特定应用程序正在生成许多大型alloc / dealloc,那么使用mmap进行这些分配可以是一种解决方案,可以将内存映像大小降低一点。

答案 4 :(得分:0)

我不能确定会发生什么,但问题(如果是问题)是可重复的。

我认为这是一个内存池问题,因为STL在使用的特定分配器中这样做了。 但是,向下钻取列表&lt;&gt;,我发现只是一个“new_allocator”,它不再执行全局新运算符的返回结果:

pointer
 allocate(size_type __n, const void* = 0)
 { return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp))); }

据我所知,行为会转到glibc或stdlibc ++内存处理。 在我的快速浏览中,如果自定义分配器肯定会有不同的行为,我无法弄清楚如何在不实现自定义分配器的情况下实现此行为。

我尝试了没有STL的另一个测试,然后我可以看到资源上下成长。 我建议创建一个自定义分配器,在数组中使用placement new分配任意数量的元素,并关心分配/解除分配这些数组。 Logic告诉我,资源使用必须像“非STL”测试一样。

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

注意:“三巨头”规则对此没有影响。据我所知,没有内存泄漏对象的内容不相关。布鲁诺可以完成数据复制,自我分配检查等,但只是做了一个空的复制构造函数来说明它的观点。

答案 5 :(得分: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),只有在程序完成后才释放内存。我在2.6.18-6-xen-amd64内核上使用4.4.5-1 g ++在Debian Squeeze上运行它。我不知道它在其他系统上是如何工作的,但是看到123个字节的额外块会导致不同的程序行为,很有可能它不起作用 - 但请相信我对于这些设置它起作用: - )< / p>

PS。任何人都可以解释为什么33和123值的额外块会导致不同的行为吗?