【问题标题】:How to interpret or narrow down the cause of a memory leak in this visual leak detector output?如何解释或缩小此可视泄漏检测器输出中内存泄漏的原因?
【发布时间】:2016-03-05 12:54:15
【问题描述】:

如何缩小原因以找到此 Visual Leak Detector 输出中报告的内存泄漏的原因?

问题不是为我调试这个特定的代码,而是如何解决这样的问题。 Visual Leak Detector 报告了许多泄漏,并且此类问题在 SO 上非常常见,因此我希望得到一个不那么具体但更笼统的答案,以便它不仅在这种特殊情况下有所帮助,而且对其他人也有更多帮助。

---------- Block 305 at 0x00000000FCBFBBB0: 64 bytes ----------
  Leak Hash: 0x7DAD966C, Count: 1, Total 64 bytes
  Call Stack (TID 11728):
  ucrtbased.dll!malloc()
e:\programme (x86)\microsoft visual studio 14.0\vc\include\xmemory0 (901): Shady.exe!std::_Wrap_alloc<std::allocator<std::_List_node<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,std::unique_ptr<Texture,std::default_delete<Texture> > >,void * __ptr64> > >::allocate()
e:\programme (x86)\microsoft visual studio 14.0\vc\include\list (730): Shady.exe!std::_List_alloc<std::_List_base_types<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,std::unique_ptr<Texture,std::default_delete<Texture> > >,std::allocator<std::pair<std::basic_string<char,std::char_traits<char>,st() + 0x19 bytes
e:\programme (x86)\microsoft visual studio 14.0\vc\include\list (716): Shady.exe!std::_List_alloc<std::_List_base_types<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,std::unique_ptr<Texture,std::default_delete<Texture> > >,std::allocator<std::pair<std::basic_string<char,std::char_traits<char>,st()
e:\programme (x86)\microsoft visual studio 14.0\vc\include\list (631): Shady.exe!std::_List_alloc<std::_List_base_types<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,std::unique_ptr<Texture,std::default_delete<Texture> > >,std::allocator<std::pair<std::basic_string<char,std::char_traits<char>,st() + 0xC bytes
e:\programme (x86)\microsoft visual studio 14.0\vc\include\list (818): Shady.exe!std::_List_buy<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,std::unique_ptr<Texture,std::default_delete<Texture> > >,std::allocator<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > con()
e:\programme (x86)\microsoft visual studio 14.0\vc\include\list (896): Shady.exe!std::list<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,std::unique_ptr<Texture,std::default_delete<Texture> > >,std::allocator<std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<char> > const ,s()
e:\programme (x86)\microsoft visual studio 14.0\vc\include\xhash (197): Shady.exe!std::_Hash<std::_Umap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::unique_ptr<Texture,std::default_delete<Texture> >,std::_Uhash_compare<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::hash<std() + 0x1A bytes
e:\programme (x86)\microsoft visual studio 14.0\vc\include\unordered_map (119): Shady.exe!std::unordered_map<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::unique_ptr<Texture,std::default_delete<Texture> >,std::hash<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std::equal_to<std::basic_string()
e:\repositories\shady\src\texture\texturemanager.h (39): Shady.exe!TextureManager::TextureManager() + 0x44 bytes
e:\repositories\shady\src\engine.h (53): Shady.exe!Engine::Engine() + 0x65 bytes
e:\repositories\shady\src\engine.cpp (97): Shady.exe!main() + 0x1D bytes
f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (75): Shady.exe!invoke_main()
f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): Shady.exe!__scrt_common_main_seh() + 0x5 bytes
f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): Shady.exe!__scrt_common_main()
f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): Shady.exe!mainCRTStartup()
KERNEL32.DLL!BaseThreadInitThunk() + 0x22 bytes
ntdll.dll!RtlUserThreadStart() + 0x34 bytes
  Data:
B0 BB BF FC    B4 01 00 00    B0 BB BF FC    B4 01 00 00     ........ ........
CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........

engine.cpp 97Engine engine = Engine();,它构造了这个类的一个对象

class Engine{
    Engine() : _logger("Engine"){}
    TextureManager _textureManager;
    Logger mutable _logger;
};

texturemanager.h 39 是此类的TextureManager() :_logger("TextureManager"){};

class TextureManager{
TextureManager() :_logger("TextureManager"){};
~TextureManager() {
    for (const auto& kv : _textures) {
        GLuint h = kv.second->getTextureHandle();
        glDeleteTextures(1, &h);
    }
}
std::unordered_map<std::string, std::unique_ptr<Texture>> _textures;
Logger mutable _logger;
};

我是否正确理解 unordered_map 导致泄漏的输出?当我尝试完全移至sharedunique pointers 时,此代码中没有涉及newmalloc 的直接分配。传递给构造函数并重用并存储在类中的字符串是否存在问题?从我读到的 std::string 确实管理它自己的内存。

【问题讨论】:

  • 您的记录器中有unordered_map吗?如果是这样,如何清理?
  • 否,但上面的 TextureManager 有一个无序的贴图。我没有清理 unordered_map 本身,因为我希望它能够被正确清理,还是我错了?
  • 假设您的TextureManager 本身已被销毁(这可能在Engine 被销毁时发生,那么unordered_map 也应该被清理。
  • 您使用的是哪个编译器/STL?可能值得创建一个简单的程序,它有一个 std::unordered_map&lt;std::string, std::unique_ptr&lt;char&gt;&gt;,您可以在其中填充一些条目然后退出,例如 ideone.com/a3hIaj
  • 我试过这个kfsone,它没有泄漏。我正在使用 Visual Studio 2015。

标签: c++ memory-leaks


【解决方案1】:

如何缩小原因以找到内存泄漏的原因

您可能在编译器使用的 STL 实现中偶然发现了内存泄漏。缩小范围的最佳方法是消除尽可能多的罪魁祸首,并建立一个除了使用看似有罪的unordered_map之外什么都不做的SSCCE或MVCE。

#include <memory>
#include <string>
#include <unordered_map>

int main()
{
    std::unordered_map<std::string, std::unique_ptr<std::string>> uomap;  //1

    uomap["hello"] = std::make_unique<std::string>("world");  //2

    return uomap.size() - 1;
}

http://ideone.com/a3hIaj

使用检漏仪启动程序并确定您是否仍然看到泄漏。如果您这样做了,恭喜您找到错误,您需要将错误报告给您的 STL 供应商。

顺便说一句,如果您使用的是 Microsoft 的实现,您可能会注意到 unordered_map 的构造涉及许多 malloc 会很有趣。您可以通过在标记为//1//2 的行上设置断点并启动程序来验证这一点。

当断点 #1 命中时,选择 Debug -> New Breakpoint -> Function Breakpoint 并在 'malloc' 上设置断点。现在按 F5 - 如果您想查看 STL/CRT 中的堆栈跟踪,请选中“Microsoft Symbol Servers”框并单击 OK。

你会发现仅仅在unordered_map的构造中就有3-4次malloc调用。

【讨论】:

    【解决方案2】:

    内存泄漏检测的一个常见问题是内存泄漏本质上就是在检测器分析快照时没有释放内存。

    看起来最常见的误报内存泄漏警告是静态或全局变量,这些变量在创建内存泄漏转储时未释放,但会在稍后释放。

    如果您对某些代码泄漏的原因不是很深刻,那么这会使查找实际泄漏变得更加乏味和困难。选择的工具可能会在没有泄漏的情况下报告泄漏,但您可能不明白原因。

    检查任何报告的内存泄漏的第一件事是

    • 是否涉及静态/全局变量?
      • 是否有必要处理它们,或者在释放上下文时它们会被正确清理?
    • 是否正确清理了容器?
    • 是否涉及继承?
      • 如果子类的实例存储在任何基类的指针中,是否所有基类都包含适当的虚拟析构函数?
    • 内存是否分配有newmalloc或类似的?
      • 此内存是否已使用 deletedelete[]free 或类似名称进行清理?

    类似于Comment Out Debugging-技术,可以使用内存快照来缩小内存泄漏的范围。以下示例适用于 Visual Studio,但同样的想法也可用于任何允许内存快照的功能。

    //Create 3 Memory states, where s3 is the difference
    _CrtMemState s1, s2, s3;
    //Snapshot the first time
    _CrtMemCheckpoint(&s1);
    //your questionable code
    //Snapshot the second time
    _CrtMemCheckpoint(&s2);
    //If there is a difference between both states, there is a memory leak
    if _CrtMemDifference(&s3, &s1, &s2)
        _CrtMemDumpStatistics(&s3);
    

    这个想法可以包装成宏和类的组合,以允许适当的上下文利用。结合#ifdef _DEBUG 方法仅包含DEBUG 构建的代码,您可以将内存泄漏检测留在代码中而不会损失性能,因为这些宏将被no-op 替换为non-DEBUG 构建,而那些应该被任何现代编译器优化。

    封装在宏和类以及堆栈中,可按如下方式使用:

    #include <MemoryLeakDetector.h>
    void foo() {
        BEGINMEMORYLEAKCHECK();
        int * j = new int;
        ENDMEMORYLEAKCHECK();/*Leak 2 of 4 bytes detected.*/
    }
    int main(void)  {
        /*Because we  use a global stack we can nest our macro use!*/
        BEGINMEMORYLEAKCHECK();
        BEGINMEMORYLEAKCHECK();
        int * i = new int;
        ENDMEMORYLEAKCHECK();/*Leak 1 of 4 bytes detected.*/
        delete i;/*Leak 1 is closed.*/
        foo();/*but we generate a leak 2 of 4 bytes inside of foo*/
        ENDMEMORYLEAKCHECK();/*Leak 2 of 4 bytes is detected here as well*/
    }
    

    下面的MemoryLeakDetector.h 使用std::stackstd::unique_ptr&lt;MemoryLeakDetector&gt; 来正确处理不同的堆栈上下文。这允许嵌套使用我们的宏。我们使用std::unique_ptr 不仅可以避免MemoryLeakDetector 代码中的泄漏,而且还允许我们使用std::move 而不是复制对象。

    MemoryLeakDetector.h:

    #pragma once
    #ifdef _DEBUG
        //#include <vld.h> /* Visual Leak Detector Memory Leak Detection*/
        #include <crtdbg.h> /*VS Memory Leak Detection*/
        #include <stack>
        #include <memory>
        #include <sstream>
        #include "Windows.h"
    
    class MemoryLeakDetector {
    public:
        MemoryLeakDetector() {};
        _CrtMemState MEMORYLEAKSTATISTICS1;
        _CrtMemState MEMORYLEAKSTATISTICS2;
        _CrtMemState MEMORYLEAKSTATISTICS3;
        static std::stack<std::unique_ptr<MemoryLeakDetector>>& stack() {
            static std::stack<std::unique_ptr<MemoryLeakDetector>> s;
            return s;
        }
    };
    
    
    #define DBOUT( s )                              \
    do{                                         \
       std::ostringstream os;                   \
       os << s;                                 \
       OutputDebugString(os.str().c_str() );    \
    }while(0)
    
    #define BEGINMEMORYLEAKCHECK() do{std::unique_ptr<MemoryLeakDetector> ___memoryleakdetector___ = std::make_unique<MemoryLeakDetector>();\
                                    MemoryLeakDetector::stack().push(std::move(___memoryleakdetector___));\
                                    _CrtMemCheckpoint(&MemoryLeakDetector::stack().top().get()->MEMORYLEAKSTATISTICS1);\
                                    }while(0)           
    #define ENDMEMORYLEAKCHECK() do{if(MemoryLeakDetector::stack().size()==0){DBOUT("\n"<<__FILE__<<"("<<__LINE__<<"):"<<"<"<<__FUNCTION__<<" ENDMEMORYLEAKCHECK without BEGINMEMORYLEAKCHECK detected\n");\
                                break;}\
                                std::unique_ptr<MemoryLeakDetector> ___memoryleakdetector___ = std::move(MemoryLeakDetector::stack().top());MemoryLeakDetector::stack().pop();\
                                _CrtMemCheckpoint(&___memoryleakdetector___->MEMORYLEAKSTATISTICS2);\
                                if (_CrtMemDifference(&___memoryleakdetector___->MEMORYLEAKSTATISTICS3, &___memoryleakdetector___->MEMORYLEAKSTATISTICS1, &___memoryleakdetector___->MEMORYLEAKSTATISTICS2)){\
                                DBOUT("\n"<<__FILE__<<"("<<__LINE__<<"):"<<"<"<<__FUNCTION__<<"> MLD detected a leak\n");\
                                _CrtMemDumpStatistics(&___memoryleakdetector___->MEMORYLEAKSTATISTICS3);\
                                DBOUT("\n\n");\
                                std::cerr << "MLD Leak detected "<<__FILE__<<"("<<__LINE__<<")"<< std::endl;\
                                }}while(0)
    #else
        #define BEGINMEMORYLEAKCHECK() do{}while(0)
        #define ENDMEMORYLEAKCHECK() do{}while(0)
        #define DBOUT( s )  do{}while(0)
    #endif
    

    【讨论】:

      猜你喜欢
      • 2011-12-13
      • 1970-01-01
      • 1970-01-01
      • 2012-07-16
      • 2023-03-08
      • 2013-03-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多