ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

linux-带有std map和shared_ptr的奇怪内存行为

2019-11-21 01:02:09  阅读:539  来源: 互联网

标签:gcc4-7 c11 memory-leaks linux 64-bit


下面的代码在我的Debian机器上引起了奇怪的内存行为.
即使清除了映射后,htop仍显示该程序仍使用大量内存,这使我认为存在内存泄漏.奇怪的事实是它仅在某些情况下出现.

#include <map>
#include <iostream>
#include <memory>


int main(int argc, char** argv)
{
    if (argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " <1|0> " << std::endl;
        std::cout << "1 to insert in the second map and see the problem "
                "and 0 to not insert" << std::endl;
        return 0;
    }

    bool insertion = atoi(argv[1]);
    std::map<uint64_t, std::shared_ptr<std::string> > mapStd;
    std::map<uint64_t, size_t> counterToSize;
    size_t dataSize = 1024*1024;
    uint64_t counter = 0;

    while(counter < 10000)
    {
        std::shared_ptr<std::string> stringPtr =
                std::make_shared<std::string>(dataSize, 'a');
        mapStd[counter] = stringPtr;

        if (insertion)
        {
            counterToSize[counter] = dataSize;
        }
        if (counter > 500)
        {
            mapStd.erase(mapStd.begin());
        }
        std::cout << "\rInserted chunk " << counter << std::flush;

        counter++;
    }

    std::cout << std::endl << "Press ENTER to delete the maps" << std::endl;
    char a;
    std::cin.get(a); // wait for ENTER to be pressed

    mapStd.clear();   // clear both maps
    counterToSize.clear();

    std::cout << "Press ENTER to exit the program" << std::endl;

    std::cin.get(a); // wait for ENTER to be pressed
    return 0;
}

说明:

该代码在堆栈上创建了两个映射(但是如果在堆上创建,则问题是相同的).然后,它将字符串的std :: shared_ptr插入第一张地图.每个字符串的大小为1MB.一旦插入了500个字符串,每个新插入的字符串都会删除第一个,因此映射使用的总内存始终等于500MB.当总共插入了10000个字符串时,程序将等待用户按ENTER键.如果启动程序并将1作为第一个参数传递,那么对于第一个映射中的每个插入,还将对第二个映射进行另一个插入.如果第一个参数为0,则不使用第二个映射.首次按下ENTER键后,将清除两个地图.该程序仍在运行,并再次等待按下ENTER键,然后退出.

这是事实:

>在我的64位Debian 3.2.54-2上,按ENTER键(因此清除了映射后),并且以1作为第一个参数启动程序(因此在第二个映射中插入)后,htop指示该程序仍然使用500MB的内存!如果程序以0作为第一个参数启动,则正确释放了内存.
>本机使用g 4.7.2和libstdc .so.6.0.17.我已经尝试过g 4.8.2和libstdc .so.6.0.18,同样的问题.
>我已经尝试了g 4.9.2和libstdc .so.6.0.20的64位Fedora 21,同样的问题.
>我已经在32位Ubuntu 14.04上使用g 4.8.2和libstdc .so.6.0.19进行了尝试,但问题没有出现!
>我已经在32位Debian 3.2.54-2上尝试了g 4.7.2和libstdc .so.6.0.17,问题没有出现!
>我已经在64位Windows上尝试过,但没有出现问题!
>在存在问题的计算机上,如果将要清除映射的行反转(因此如果先清除uint64_t,size_t映射,则问题将消失!

有人对此有一个解释吗?

解决方法:

我建议先看here,再看here.基本上,libc malloc首先使用mmap进行“大”分配(> 128k),使用brk / freelists进行小分配.一旦释放了一个较大的分配,它就会尝试在可能使用malloc的位置调整大小,但前提是该大小小于最大值(在第一个链接中定义).在32位的情况下,您的字符串远高于最大值,因此它继续对大型分配使用mmap / munmap,并且仅将较小的映射节点分配放入使用sbrk从系统检索的内存中.这就是为什么在32位系统上看不到“问题”的原因.

另一位是碎片之一,空闲时尝试合并内存并将其返回给系统.默认情况下,free会将小块粘贴到空闲列表上,以便它们准备下一个请求.如果在堆顶部释放了足够大的块,它将尝试将内存返回给系统see comment here.阈值为64K.

如果您传递1,则您的分配模式可能会使counterToSize映射的某些元素靠近堆的顶部,从而防止它在字符串的最后一次释放时被释放. counterToSize映射内的各种对象的释放量不足以触发阈值.

如果切换.clear()调用的顺序,则会发现内存已按预期释放.另外,如果您要分配大量内存并在清除后立即释放它,则会触发释放. (在这种情况下,大只需要超过128个字节-用于触发快速bin的最大大小.(即,具有该大小的free且分配的空间小于该大小的free进入列表.

我希望这很清楚.基本上,这不是真正的问题.您已经映射了一些页面.您没有在它们上使用任何东西,但是可能释放它们的最后一个免费软件太小,无法触发该代码路径.下次尝试分配某些内容时,它将从您已有的内存中提取(您可以在不增加内存的情况下再次执行整个循环).

哦,如果您当时确实需要返回页面,则可以手动调用malloc_trim()并强制它进行合并/清除.

标签:gcc4-7,c11,memory-leaks,linux,64-bit
来源: https://codeday.me/bug/20191121/2048000.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有