【问题标题】:C++ throwing exception causes memory leakC++ 抛出异常导致内存泄漏
【发布时间】:2014-03-15 03:32:38
【问题描述】:

我最近遇到了内存泄漏问题。我对该问题进行了很长时间的故障排除,随后发现抛出异常(我使用自己的异常类)会导致此内存泄漏。抛出异常的代码如下:

HINSTANCE lib = LoadLibrary(path.c_str());

if(!lib)
{
    DWORD werror = GetLastError();
    ostringstream stream;
    stream << werror;
    string errstring = "Error " + stream.str();
    errstring.append(" - " + libraryName);

    throw LibraryLoadException(MOD_ERROR_LIB_LOAD, errstring.c_str());
}

生成的输出如下所示:

Detected memory leaks!
Dumping objects ->
{351} normal block at 0x0044D208, 32 bytes long.
 Data: <Error 126 - note> 45 72 72 6F 72 20 31 32 36 20 2D 20 6E 6F 74 65 
{347} normal block at 0x0043BD98, 8 bytes long.
 Data: <4 >     > 34 F2 3E 00 00 00 00 00 
{344} normal block at 0x0043FDE8, 32 bytes long.
 Data: <126             > 31 32 36 CD CD CD CD CD CD CD CD CD CD CD CD CD 
{302} normal block at 0x004409D8, 8 bytes long.
 Data: <4 >     > 34 F3 3E 00 00 00 00 00 
{301} normal block at 0x0043FAF0, 8 bytes long.
 Data: <P >     > 50 F3 3E 00 00 00 00 00 
Object dump complete.

从 Visual Studio Leak CrtDbg 的输出中可以看出,if 块中使用的对象的实际值。但是,包括异常本身(及其所有属性)在内的所有这些对象都是在堆栈上分配的,所以我不会忘记在堆上释放某些东西。 我对此进行了经验测试,泄漏肯定是由 if 块中的对象引起的(在删除了字符串、DWORD 和流等多个对象后,泄漏变得更少)。

谁能告诉我这里我在做什么(或什么)错了?

提前谢谢你

【问题讨论】:

  • 请发布一个完整的示例,以及捕获异常的catch 子句。
  • “libraryName”从何而来?正如您发布的那样,这段代码真的是独立的,还是类或结构的成员函数的一部分?如果是后者,那么内存泄漏可能是由于您分配的包含此函数的对象未销毁而导致的。

标签: c++ exception memory-leaks throw


【解决方案1】:

好的,我设法将泄漏减少到原来的 5 个中的两个:

Dumping objects ->
{312} normal block at 0x0045FDC8, 8 bytes long.
Data: <( (     > 28 ED 28 00 00 00 00 00 
{311} normal block at 0x0045F810, 8 bytes long.
Data: <D (     > 44 ED 28 00 00 00 00 00 
Object dump complete.

我使用了 _CrtSetBreakAlloc(x) 函数,其中 x 是泄漏的编号(例如,在上述情况下为 311 或 312)并发现未分配的内存分配在哪里。真的很难相信,但分配确实发生在以下几行:

string errstring = "Error " + stream.str();
and
errstring.append(" - " + libraryName);

我通过在堆上动态分配字符串和流来消除泄漏,然后创建异常并将其存储在临时变量中,随后释放字符串和流变量,最后抛出异常本身:

DWORD werror = GetLastError();
    ostringstream *stream = new ostringstream();
    *stream << werror;
    string *errstring = new string("Error ");
    errstring->append(stream->str());
    errstring->append(" - ");
    errstring->append(libraryName);

    ProbeCoreException e = LibraryLoadException(MOD_ERROR_LIB_LOAD,
            errstring->c_str());

    delete errstring;
    delete stream;

    throw e;

在将字符串参数传递给“加载”函数本身的过程中发生了最后两次分配(再次令人难以置信):

loader.load("notexisting.dll", "TEST", &callbackFunction);

我正在考虑由于类是单例而发生的泄漏,但是,该类是根据防泄漏单例规则创建的,但在这里提到:

C++ Singleton design pattern

似乎摆脱剩余泄漏的唯一机会是将字符串指针作为参数传递,然后显式释放它们...

【讨论】:

    【解决方案2】:

    至于cmet要求更详细的代码,这里是导致内存泄漏的方法:

    void ModuleLoader::load(std::string libraryName, std::string type, void(CALLBACK * receiveData)(Message))
    {
    path = s2ws(libraryName);   // conversion to wide string
    
    HINSTANCE lib = LoadLibrary(path.c_str());
    
    if(!lib)
    {
        DWORD werror = GetLastError();
        ostringstream stream;
        stream << werror;
        string errstring = "Error " + stream.str();
        errstring.append(" - " + libraryName);
    
        throw LibraryLoadException(MOD_ERROR_LIB_LOAD, errstring.c_str());
    }
    
    DllModule *module = new DllModule(libraryName, lib);
    module->setModType(type);
    
    try
    {
        startModule(module, receiveData);
        moduleMap.insert(std::pair<std::string, DllModule *>(type, module));
    }
    catch (ProbeCoreException e)
    {
        delete module;
        throw e;
    }
    }
    

    是加载动态模块的单例类的方法,定义如下:

    class ModuleLoader 
    {
    // Function pointer definitions
    typedef void (*StopFuncPointer)();
    typedef int (*StartFuncPointer)(void(CALLBACK * receiveData)(Message));
    typedef void (*SetDataFunctionPointer)(Message);
    
    private:
    
    std::map<std::string, DllModule *> moduleMap;   // map of loaded modules
    
    std::wstring path;
    
    std::wstring s2ws(const std::string &s);
    
    void startModule(DllModule * module, void(CALLBACK * receiveData)(Message));    
    
    void stopModule(DllModule * module);
    
    // singleton functions
    ModuleLoader() {};  // private constructor
    ModuleLoader(ModuleLoader const&);
    void operator = (ModuleLoader const&);
    
    public:
    
    void load(std::string libraryName, std::string type, void(CALLBACK * receiveData)(Message));
    
    void unload(std::string libraryType);
    
    void unloadAll();
    
    vector<DllModule> getLoadedModules();
    int containsModuleType(string modType);
    
    HINSTANCE getModuleLibraryByType(std::string type);
    
    // singleton getInstance function
    static ModuleLoader & getInstance()
    {
        static ModuleLoader instance;
        return instance;
    }
    };
    

    s2ws 方法将普通字符串转换为宽字符串(我贴出来以防万一):

    std::wstring ModuleLoader::s2ws(const std::string& s)
    {
    int len;
    int slength = (int)s.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); 
    wchar_t* buf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
    std::wstring r(buf);
    delete[] buf;
    return r;
    }
    

    我检查了很多次,并且在抛出异常时释放堆对象应该在应用程序的各个级别进行。 此外,如果我确实删除了 DWORD、ostringstream 和字符串对象(在堆栈上分配),内存泄漏会减少……所以它也必须与这些相关。我无法想象删除这部分代码应该如何帮助其他地方的堆内存释放:

    DWORD werror = GetLastError();
    ostringstream stream;
    stream << werror;
    string errstring = "Error " + stream.str();
    errstring.append(" - " + libraryName);
    

    【讨论】:

      猜你喜欢
      • 2014-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-13
      • 1970-01-01
      • 2016-03-19
      • 2017-02-20
      • 2019-10-21
      相关资源
      最近更新 更多