C++ delete 对象但内存没被回收?

2018-12-13 12:09:49 +08:00
 zhiqiang

这个对象是一个 std::vector<boost::circular_buffer>,我建了几千个这种对象,在 new 时检查内存,大概会增加占用了 10G 内存。

但随后 delete 这些对象,内存却没有释放。导致程序多来回几次之后占用上白 G 内存然后就挂了。

不知道有人遇到过同样的案例没?

系统:ubuntu 16.04, gcc 5.4, boost 1.0.0.68 。

检查程序实时占用的内存我是看 /proc/self/status 的 VmRSS: 那一行。写了一个函数在 new 和 delete 前后实时获取和显示这个值。

还有一个奇怪的现象,上面占用内存是在程序多调用了一个模块( dlopen 了一个 so 文件)后才会出现。如果没有那个模块,内存增减都是正常的。而这个模块,里面并未操作这些对象。

4625 次点击
所在节点    C
22 条回复
willm
2018-12-13 12:15:47 +08:00
delete 只是标记内存不再使用,并不一定 memset 为 0。你检测是否回收的方法是有问题的
cepheus
2018-12-13 12:17:21 +08:00
o 改成智能指针试试什么?
willm
2018-12-13 12:22:40 +08:00
额,看错了。我以为你是直接获取被释放的对象来检测的。

会不会是这个模块重载了 delete 操作符
zhiqiang
2018-12-13 12:26:31 +08:00
@willm 重载是不可能的,我没写过这么底层的东西,要么就是 boost 里面的实现有重载。

我怀疑跟 runtime link 有关系。
wutiantong
2018-12-13 13:39:36 +08:00
boost::circular_buffer 的模版参数是什么?
你 new 的对象是 vector 还是 circular_buffer 还是 circular_buffer 的模版参数?
zhiqiang
2018-12-13 14:19:03 +08:00
@wutiantong 在扇贝 C++写得多不?

我上面写的有一定简化。

原始对象其实是一个 T,里面有两个成员都是 vector of cirular_buffer of int。

我 new 和 delete 的都是 T。
GeruzoniAnsasu
2018-12-13 14:26:01 +08:00
我遇到过加载一个 so 内存泄露的问题,dlopen/close 内存是平衡的,但中途调多一个 api 就不平衡了
然后开了 asan 也没发现泄露,查了两天最后发现是 so 会自己 mmap 一块内存自己做内存管理,然后为了保证速度在 deallocate 之后还会预留一块内存以便下次分配加速,这时候 dlclose 它并不会自行 munmap,造成的泄露


我怀疑你遇到的是不是也是 hook 了 allocate/deallocate 造成的问题
wutiantong
2018-12-13 14:32:44 +08:00
@zhiqiang 好像就我一个人在写 C++...

我感觉楼上说得有道理,虽然我不懂这块。。。
zhiqiang
2018-12-13 14:35:46 +08:00
@GeruzoniAnsasu 我多挂载的 so 也是我写的,里面很简单。唯一有问题的,可能在这个 so 也链接 boost circular_buffer 以及我那个类 T 的实现。

现在看来可能是 circular_buffer 的 allocate/deallocate 可能有问题。
arzterk
2018-12-13 14:42:28 +08:00
std::vector<T>里面的 capacity 内存是要手动释放的呀,看过 STL 源码都知道的,参考这个 https://en.cppreference.com/w/cpp/container/vector/shrink_to_fit
wutiantong
2018-12-13 14:59:08 +08:00
@arzterk 都已经 delete 了 shrink_to_fit 就不重要了吧。
wwqgtxx
2018-12-13 15:05:43 +08:00
其实你可以考虑贴出一个可以复现且经过简化的代码放在 gist 上,比这样大家都在猜的效率高得多
zhiqiang
2018-12-13 15:29:58 +08:00
@wwqgtxx 能简化复现的 bug,不会贴到这里麻烦网友们。
cxytz01
2018-12-13 15:49:45 +08:00
vector 使用的 allocator 函数有几种:其中一种是不会释放内存给 OS,只会回收给 STL 内存池,这很霸道;一种是直接 malloc,STL 内部不提供内存池;还有其他 alloctator 函数。
vector 的模板声明是:template<class T, class Allocator = std::allocator<T>> class vector;

参考上面,选择适合你的 allocator 函数。另外由于编译器版本不同,默认选择的 allocator 函数也不一样,有的版本默认 allocator 函数选择就是不回收内存的 allocator 函数。

以上知识点参考侯捷大师:<<c++内存管理>>课程

年代久远,已经记不清细节。
codehz
2018-12-14 00:00:40 +08:00
请务必检查下加载的模块的 ABI 兼容性,比如说编译器,各种库的版本等等,如果有 ABI 兼容问题,也可能导致出现预期之外的行为...
zhiqiang
2018-12-14 08:47:23 +08:00
@codehz ABI 兼容性应该没问题。那个 so 也是我自己编译的,同一系统同样环境。

再说能提示下,怎么检查 ABI 兼容性问题吗?
zhiqiang
2018-12-14 08:58:21 +08:00
再补充一下我试验的结果:

我确定 boost::circular_buffer 这个东西的内存实现和常规的不太一样,具体表现在分配的空间如果没有实际用上,那么并不会占用太多的内存。有点类似于按需扩容,但我看 boost 的文档,这东西应该是在初始化一次性分配内存的。

The circular_buffer only allocates memory when created, when the capacity is adjusted explicitly, or as necessary to accommodate resizing or assign operations.

但我也没试验出 delete 之后的内存不释放的情况。可能跟上面发现的特性有关。
zhiqiang
2018-12-14 09:30:04 +08:00
查看了 boost::circular_buffer 的源代码,它用的 allocator 就是 std::vector::allocator,但跟 std::vector 不一样在于,circular_buffer 不会对内存初始化。

内存没用不占用可能是操作系统搞的,虽然分配了地址,但不用就不分配实际物理地址,导致不会实际占用物理内存。

delete 不回收内存的原因还是没搞清楚。
farseeraliens
2018-12-14 13:07:21 +08:00
heapprofiler 看内存泄漏。asan 是用来看内存非法访问的。
zhiqiang
2018-12-14 14:19:31 +08:00
虽然还没搞清楚确切的原因,但很可能跟分配了大量细碎的内存片段有关。我打算把 circular_buffer 都用内存池管理起来。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/517172

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX