C++ Linux分配器不会释放小块内存

C++ Linux分配器不会释放小块内存,c++,memory,heap,allocation,C++,Memory,Heap,Allocation,LinuxGlibc分配器的行为似乎很奇怪。希望有人能对此有所了解。以下是我拥有的源文件: first.cpp: #include <unistd.h> #include <stdlib.h> #include <list> #include <vector> int main() { std::list<char*> ptrs; for(size_t i = 0; i < 50000; ++i) { ptrs

LinuxGlibc分配器的行为似乎很奇怪。希望有人能对此有所了解。以下是我拥有的源文件:

first.cpp:

#include <unistd.h>
#include <stdlib.h>
#include <list>
#include <vector>

int main() {

  std::list<char*> ptrs;
  for(size_t i = 0; i < 50000; ++i) {
    ptrs.push_back( new char[1024] );
  }
  for(size_t i = 0; i < 50000; ++i) {
    delete[] ptrs.back();
    ptrs.pop_back();
  }

  ptrs.clear();

  sleep(100);

  return 0;
}
请注意驻留内存的大小。首先,驻留内存大小为53016k。第二,它是1024k。由于某种原因,First从未将分配释放回内核


为什么第一个程序不向内核释放内存,而第二个程序却这样做?我知道第一个程序使用一个链表,链表可能会在我们释放的数据所在的页面上分配一些节点。但是,这些节点应该被释放,因为我们正在弹出这些节点,然后清除链接列表。如果您通过valgrind运行这两个程序中的任何一个,那么它返回时不会出现内存泄漏。可能发生的情况是,first.cpp中的内存碎片化,而second.cpp中的内存碎片化。但是,如果一个页面上的所有内存都被释放了,那么该页面如何不被释放回内核呢?内存被释放回内核需要什么条件?如何修改first.cpp(继续将字符*放入列表),以便将内存释放给内核。

通常,由
new
分配的内存仅在进程终止时返回系统。在第二种情况下,我怀疑
libc
正在为非常大的连续块使用一个特殊的分配器,它确实会返回它,但是如果您的
新字符[1024]
中有任何一个被返回,我会非常惊讶,而且在许多Unice上,即使是大数据块也不会返回。

它会保留较小的数据块,以防您再次请求它们。这是一个简单的缓存优化,而不是需要关注的行为。

此行为是有意为之,glibc使用一个可调的阈值来决定是否实际将内存返回到系统或是否缓存以供以后重用。在您的第一个程序中,每个
push_back
都会进行大量的小分配,这些小分配不是一个连续的块,可能低于阈值,因此不要返回操作系统

清除列表后调用应导致glibc立即 将最上面的可用内存区域返回到系统(下次需要
sbrk
系统调用内存 是需要的。)

如果您真的需要覆盖默认行为(除非分析显示它确实有用,否则我不建议这样做),那么您可能应该使用strace和/或对 到 看看你的程序中实际发生了什么,也许你会习惯于 调整将内存返回系统的阈值。

(编辑我的答案,因为这里确实没有任何问题。)

如前所述,这里并没有真正的问题。约翰纳森清醒地击中了他的头部

当内存利用率不是我在Linux上期望的那样时,我通常使用
mtrace
工具开始分析,并分析
/proc/self/maps
文件

mtrace
用于将代码括在两个调用周围,一个用于启动跟踪,另一个用于结束跟踪

  mtrace();
  {
      // do stuff
  }
  muntrace();
只有在设置了
MALLOC_TRACE
环境变量时,
mtrace
调用才处于活动状态。它指定mtrace日志记录输出的文件名。然后可以分析该日志输出是否存在内存泄漏。名为
mtrace
的命令行程序可用于分析输出

$ MALLOC_TRACE=mtrace.log ./a.out
$ mtrace ./a.out mtrace.log
/proc/self/maps
文件提供当前程序正在使用的内存映射区域列表,包括匿名区域。它可以帮助识别特别大的区域,然后需要额外的侦查来确定该区域与什么相关。下面是一个将
/proc/self/maps
文件转储到另一个文件的简单程序

void dump_maps (const char *outfilename) {
  std::ifstream inmaps("/proc/self/maps");
  std::ofstream outf(outfilename, std::ios::out|std::ios::trunc);
  outf << inmaps.rdbuf();
}
void dump_映射(常量字符*outfilename){
std::ifstream inmap(“/proc/self/maps”);
std::ofstream Outp(OutfielName,std::ios::out | std::ios::trunc);

outp使用shrink to fit,如上所述。在本例中,执行
std::list().swap(ptrs)
。恐怕这里还有其他问题……这是我的新程序:int main(){{std::list ptrs;for(size_t I=0;I<50000;++I){ptrs.push_back(new char[1024])}for(size_t i=0;i<50000;++i){delete[]ptrs.back();ptrs.pop_back()}ptrs.clear();std::list().swap(ptrs);}sleep(100);返回0;}运行ps的结果是一样的:davidw 9961 0.0 0.3 64344 53016 pts/4 S 00:31 0:00./first它被标记为C,因为在C中使用malloc/free会遇到同样的问题。我在想将来用C编程的人可能会发现这一点很有用。你验证过你的第二个程序实际分配内存吗?我记得读过r最近关于优化掉
malloc
/
free
对,中间没有实际使用结果的代码,同样的逻辑也适用于
new
/
delete
对。@hvd不应该,至少在没有完整程序分析的情况下不应该。调用
operator new
operator delete
是obs关于C++中的可维护行为。关于MalCopyTrim:自从GLIMC 2.8,这个函数释放了所有的ArNas中的内存和所有的空闲页面中的所有块。在GLUBC 2.8之前,这个函数只释放了主竞技场堆堆顶部的内存。
  mtrace();
  {
      // do stuff
  }
  muntrace();
$ MALLOC_TRACE=mtrace.log ./a.out
$ mtrace ./a.out mtrace.log
void dump_maps (const char *outfilename) {
  std::ifstream inmaps("/proc/self/maps");
  std::ofstream outf(outfilename, std::ios::out|std::ios::trunc);
  outf << inmaps.rdbuf();
}