std::unordered_map 析构函数不释放内存?

Posted

技术标签:

【中文标题】std::unordered_map 析构函数不释放内存?【英文标题】:std::unordered_map destructor does not free memory? 【发布时间】:2020-02-07 11:07:08 【问题描述】:

根据标准,当 std::unordered_map 的析构函数被调用时(例如,当它离开创建它的范围时),人们期望它分配的内存被释放。然而,我在多台机器上运行的一个简单实验似乎与此冲突?考虑以下程序:

    #include <chrono>
    #include <iostream>
    #include <map>
    #include <unordered_map>
    #include <memory>
    #include <thread>
    #include <vector>

    void CreateMap() 
        std::unordered_map<int, int> testMap;

        for (int i=0; i < 10000000; i++) 
            testMap[i] = 5;
        

        std::cout << "finished building map" << std::endl;

        std::this_thread::sleep_for (std::chrono::seconds(15));

        std::cout << "about to exit from CreateMap()" << std::endl;
    

    int main()
    
        CreateMap();

        CreateMap(); 

        CreateMap();
    while (true) 
        std::this_thread::sleep_for(std::chrono::seconds(1));
    

    return 0;

在我的机器上,地图构建完成时会消耗 10% 的 RAM,但最后我们在 CreateMap() 中休眠。然而,在退出 RAM 后仅下降到 8%(可以使用各种大小的地图来显示地图本身负责超过 2%)所以人们会期望 CreateMap 泄漏内存?然而,对 CreateMap() 的 3 次调用或仅一次调用似乎没有任何区别(因此内存被回收到程序而不是 RAM?)。

这可能是我不理解的操作系统内存管理的奇怪之处吗?即,程序可以释放内存以供自己将来使用(未来分配)但不能释放给操作系统(即用于内存分配在其他程序中)?

【问题讨论】:

编译器的标准(或运行时)库附带了一个堆管理。每当内存不足时,它都会尝试从操作系统获取更多。当在 C++ 中释放内存时,它不一定会返回给操作系统,但通常可能会保留在“空闲”列表中以供以后的请求使用。因此,在使用操作系统内存检查工具时,您可能看不到这些可用内存。 操作系统不必立即释放为您的进程分配的页面,因此在检查内存时仍然可以计算它们(取决于如何检查内存)。 仅供参考:CS: How is heap memory allocated to a process? 今天早上有人问了一个非常相似的问题。你可能有兴趣阅读this answer。 @Mathemagician:根据操作系统和分配器,它甚至可以通过向操作系统提供提示来协助这个过程,例如在 Linux 上,当没有剩余分配为 MADV_DONTNEED 时,它可能会 madvise mmaped 页面(通常是块大小的一小部分),这告诉操作系统它可以删除数据;如果稍后访问这些页面,操作系统只会按需提供它们并填充零。所以从技术上讲,内存仍然是“分配的”(虚拟地址空间仍在使用中,如果需要可以访问内存),但没有使用物理 RAM 或交换文件。 【参考方案1】:

你测试错了。代码不会泄漏,但释放的内存不一定可用于其他进程(您正在测量的内容) - 它可能仍会被同一进程声明以供将来分配。

例如,在移除无限循环并减少i 的限制以适应我的测试沙箱之后,我在 Valgrind 下运行代码:

valgrind --leak-check=full ./60112215   
==3396096== Memcheck, a memory error detector
==3396096== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3396096== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==3396096== Command: ./60112215
==3396096== 
finished building map
about to exit from CreateMap()
finished building map
about to exit from CreateMap()
finished building map
about to exit from CreateMap()
==3396096== 
==3396096== HEAP SUMMARY:
==3396096==     in use at exit: 0 bytes in 0 blocks
==3396096==   total heap usage: 300,044 allocs, 300,044 frees, 13,053,168 bytes allocated
==3396096== 
==3396096== All heap blocks were freed -- no leaks are possible
==3396096== 
==3396096== For lists of detected and suppressed errors, rerun with: -s
==3396096== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

如果你想真正亲自演示一下,可以多次调用CreateMap(),看看进程的内存使用量并没有增加:

int main()

    for (auto i = 0;  i < 100;  ++i) 
        CreateMap();
    

这显然是在重复使用上一次迭代中释放的内存。

【讨论】:

【参考方案2】:

确保std::unordered_map 不会泄露。您不能使用系统监视器来检查您的程序是否泄漏,期间。如果有的话,您应该查看物理内存使用情况,而不是总内存或虚拟内存。由于页面可以交换到磁盘(页面文件),因此即使物理内存也不能准确反映 RAM 的使用情况。即使那样,例如优化。操作系统或 c++ 语言级别可能会重用堆内存 (as mentioned in comment) 进行优化。涉及的因素太多,而且对于像检测泄漏内存这样微妙的事情来说,它的总价值太大了。寻找泄漏的一种简单(并且通常有效)的方法是在程序终止时查看堆,例如。使用_CrtSetDbgFlag 或类似工具。不用说,避免手动内存管理和使用所有权语义在避免代码泄漏方面大有帮助。

【讨论】:

以上是关于std::unordered_map 析构函数不释放内存?的主要内容,如果未能解决你的问题,请参考以下文章

C++ std::unordered_map 中使用的默认哈希函数是啥?

std::unordered_map 的函数指针

C++17 复制构造函数,std::unordered_map 上的深拷贝

使用 lambda 函数在 std::unordered_map 中查找最小值

std::unordered_map::extract 引用/指针失效

具有私有构造函数的 unordered_map 值类型