2010-10-01 137 views
4

我执行在Linux下我自己的图形库(Fedora 10中和CentOS 5)用gcc 4.3.2和使用STL容器,然后我发现了一些问题与记忆。当我构建我的图形时,我使用了足够多的内存来查看top或其他内存使用工具。我敢肯定,我重新分配内存(我一次又一次地回顾了代码,我使用的valgrind检查内存泄漏),但内存仍然在使用(我可以在顶部执行cat/proc查看此/ meminfo),当我再次创建图形时,它不会增加内存使用量,显然会重新使用分配的内存。STL容器内存问题

经过几天的调试,我创建了一个非常简单的代码,它有相同的问题。

#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; 
} 

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

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

谢谢!

- 布鲁诺卡波尼

+2

请注意,当你分配在车队内存和可用它在析构函数,你的代码是不是异常安全的,因为四个分配中的任何一个可能失败(如果,例如,第三分配失败,对象'一'和'b'将被泄漏)。在这种情况下最好使用像boost :: scoped_ptr这样的智能指针(它有一个非常简单的实现)。 – 2010-10-01 18:46:09

+2

@James在这里我只是简单地使用表格:没有必要为这个演示动态地分配它们。也不需要动态地分配'list'。恐怕'Bruno'来自Java/C#背景,非常需要使用'new'。 – 2010-10-01 18:58:52

+2

'MyObject'需要一个赋值操作符。否则,您将删除相同的内存块两次。 – 2010-10-01 19:00:17

回答

2

用于容器类内存使用通过处理容器的allocator(其被传递在作为构造参数std::list,并且是由std::allocator默认值)。默认分配器的实现可以选择不立即将内存返回给系统,以防止堆的过度分段。

如果你想在这个更直接的控制,你可能不得不实现自定义分配器。

+0

我调试默认的分配器,显然内存应该正常返回到系统。然后我使用类似于标准分配器(:: operator new()和placement new)的策略实现自己的分配器和我自己的列表,并且不会出现问题。 – 2010-10-01 19:18:26

4

GCC STL有抓住的内存大块,不给他们回了自己的内存管理层;有你可以设置让它使用原始新呼叫

GLIBCPP_FORCE_NEW=1 

我认为这使得自由太一个环境变量。这个env var通常在使用valgrind时使用,以便valgrind不会认为泄漏的东西

+1

ps。 env var名称改变了几次。谷歌会告诉你哪个名称是由哪个gcc版本使用 – pm100 2010-10-01 18:50:11

+0

我在google上发现该环境变量是GLIBCXX_FORCE_NEW,但我已经尝试过,但仍然没有返回内存。你使用Linux?你可以在你的机器上测试吗? – 2010-10-01 19:36:27

+0

是的Linux。不,我不会测试(sry) – pm100 2010-10-01 21:17:22

3

除了STL容器,它可能是libc自己这样做的(new/delete-malloc/free实现)。用户空间库可以自由保留内存以备后用。分配/解除分配是一项昂贵的操作(在时钟周期方面),因此很多实现尝试避免这种情况。

0

在Linux用户存储器通过系统调用BRK,向下延伸的数据指针,从而使可用于处理更多的RAM分配给进程从内核。这是内核将正常内存传递给进程的唯一方式。也可以通过使用mmap来获得内存,它允许进程指定一个起始地址(除了数据指针),但没有分配器这样做,因为它极大地增加了分配器和内核分配器必须的工作量做。由于这个原因,给内核用户的内存很容易被内核收回,直到进程终止。如果您的特定应用程序正在执行大量的alloc/deallocs,那么使用mmap来执行这些分配可能是一种解决方案,可以将内存映像大小降低一些。

0

我不能肯定地说会发生什么,但问题(如果它是一个问题)是可重复的。

我认为这是一个内存池的问题,即徘徊在STL中使用的特定分配器这样做。 向下钻取列表<>,不过,我发现,做什么更多的,全球新的运营商的返回结果只是一个“new_allocator”:

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

行为去然后到glibc或stdlibC++内存处理,我的理解。 在我快看我无法弄清楚如何circunvent这种行为,但并不实现自定义分配器也不如果自定义的分配器肯定会表现不同。

我试过,没有STL另一个测试,然后我可以看到越来越多的资源上下。 我会建议创建一个自定义分配器,用于分配数组中的任意数量的元素,并关心分配/取消分配这些数组。逻辑告诉我,资源使用必须像“非STL”测试一样。

请您尝试一下,告诉我们发生了什么。我不会做我自己,因为我现在没有时间的空闲时间,尽管我好学的;)

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

0

我遇到同样的问题,经过长时间的调试写了示例程序,其中ilustrates,这是一些内核(或者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个字节用于其他系统会导致不同的程序行为,很有可能它不起作用 - 但请相信我对于这些设置起作用:-)

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