【问题标题】:STL alternative for memory leak内存泄漏的 STL 替代方案
【发布时间】:2017-04-04 14:36:35
【问题描述】:

我是 C++ 编码和 STL 库使用的新手。我对在我正在开发的 C++ 程序上使用 STL 库非常感兴趣。但最近我尝试使用 Visual Leak 检测器,发现很多泄漏,特别是我使用 STL 库和 new 运算符的地方。

由于它是一个运行时应用程序,我还发现当我连续运行代码 4-5 天时 CPU 内存不断增加。在一些论坛和其他注意到的学习中,我了解到我需要删除/擦除std::mapstd::unordered_mapstd:set 中的内容,然后再调用其对应的clear。 我的问题是,如果我在永远不会调用的析构函数调用上执行所有这些操作(因为我的程序员永远不会停止),STL 库是否会在我每次访问它时造成内存泄漏?

有没有替代方案?

例如:common.h

struct stTagElem_t {
    uint32_t  m_unOffset;
    uint32_t  m_unArraySize;
    string    m_acTagName;
    string    m_acTagValue;
    string    m_acDataType;
} ;

typedef std::unordered_map<uint8_t, stTagElem_t> tagsList_t;

struct stMappingElem_t {
    uint16_t     m_unMemSize;
    tagsList_t   m_stTagsList;
    string       m_acEventName;
    string       m_acMapTypeName;
} ;

typedef std::unordered_map<uint32_t, stMappingElem_t> eventDataMap_t;

dataMapManager.cpp //声明为全局列表,因为dataMapManager.cpp拥有所有静态函数

eventDataMap_t sm_eventDataMapList;

所以当我使用 Visual Leak Detector 时,它会显示以下内容:

---------- Block 596 at 0x00DE8DB8: 120 bytes ----------


Leak Hash: 0xB26C3A34, Count: 1, Total 120 bytes
  Call Stack (TID 11580):
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0 (977): example.exe!std::_Wrap_alloc<std::allocator<std::_List_node<std::pair<unsigned __int64 const ,stMappingElem_t>,void *> > >::allocate()
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\list (730): example.exe!std::_List_alloc<std::_List_base_types<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> > > >::_Buynode0() + 0x11 bytes
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\list (716): example.exe!std::_List_alloc<std::_List_base_types<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> > > >::_Buyheadnode()
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\list (649): example.exe!std::_List_alloc<std::_List_base_types<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> > > >::_List_alloc<std::_List_base_types<std::pair<unsigned __int64 const ,stMappingElem_t>,std::al() + 0x8 bytes
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\list (826): example.exe!std::_List_buy<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> > >::_List_buy<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappin()
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\list (899): example.exe!std::list<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> > >::list<std::pair<unsigned __int64 const ,stMappingElem_t>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> >()
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\xhash (197): example.exe!std::_Hash<std::_Umap_traits<unsigned __int64,stMappingElem_t,std::_Uhash_compare<unsigned __int64,std::hash<unsigned __int64>,std::equal_to<unsigned __int64> >,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> >,0> >::_Hash<std::_Umap_tra() + 0xF bytes
    c:\program files (x86)\microsoft visual studio 14.0\vc\include\unordered_map (119): example.exe!std::unordered_map<unsigned __int64,stMappingElem_t,std::hash<unsigned __int64>,std::equal_to<unsigned __int64>,std::allocator<std::pair<unsigned __int64 const ,stMappingElem_t> > >::unordered_map<unsigned __int64,stMappingElem_t,std::hash<unsigned __int6()
    g:\mywork\src\datamapmanager.cpp (15): example.exe!`dynamic initializer for 'sm_eventDataMapList''() + 0xD bytes
    d:\rs1\minkernel\crts\ucrt\src\appcrt\startup\initterm.cpp (22): ucrtbased.dll!_initterm()
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (221): example.exe!__scrt_common_main_seh() + 0xF bytes
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (296): example.exe!__scrt_common_main()
    f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): example.exe!mainCRTStartup()
    KERNEL32.DLL!BaseThreadInitThunk() + 0x24 bytes
    ntdll.dll!__RtlUserThreadStart() + 0x2F bytes
    ntdll.dll!_RtlUserThreadStart() + 0x1B bytes
  Data:
    38 09 E6 00    F0 98 E8 00    CD CD CD CD    CD CD CD CD     8....... ........
    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     ........ ........
    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     ........ ........
    CD CD CD CD    CD CD CD CD                                   ........ ........

更多地图:common.h

using namespace std;

#ifdef USE_64BIT
typedef uint64_t   ID;
#else
typedef uint32_t   ID;
#endif

typedef ID EVENT_UID;

struct stEventParams_t {
    EVENT_UID  m_unEventID;
    uint8_t    *m_ptrMemAddr;
    size_t     m_dataSize;
} ;

struct stTagElem_t {
    uint32_t  m_unOffset;
    uint32_t  m_unArraySize;
    string    m_acTagName;
    string    m_acTagValue;
    string    m_acDataType;
} ;

typedef std::unordered_map<uint8_t, stTagElem_t> tagsList_t;

struct stMappingElem_t {
    uint16_t     m_unMemSize;
    tagsList_t   m_stTagsList;
    string       m_acEventName;
    string       m_acMapTypeName;
} ;

typedef std::unordered_map<EVENT_UID, stMappingElem_t> eventDataMap_t;

struct stAppBlocInfo_t {
    string   m_acAppBlocName;
    string   m_acInstanceName;
    string   m_acDataMap_r_SPError;
    string   m_acTypeName;
} ;

typedef std::unordered_map<string, string> instanceTypeList_t;

typedef struct {
    stAppBlocInfo_t    m_acServiceProvider;
    instanceTypeList_t m_acParamsList;
} stSP_Params_t;

typedef std::unordered_map<string, stSP_Params_t> serviceProvider_t;

struct stServiceParams_t {
    bool               m_bIsItOutput;
    uint8_t            m_unIdxToEvtClient;
    EVENT_UID          m_unEventID;
    string             m_acInstanceName;
    string             m_acTypeName;
    instanceTypeList_t m_acParamsList;
} ;

typedef std::unordered_map<ID, stServiceParams_t> serviceParams_t;

struct stStatusElem_t {
    bool                m_bErrorPresent;
    std::string         m_acSPError;
    instanceTypeList_t  m_ServiceStatus;
};

typedef std::unordered_map<CSI_base *, stSP_Params_t > spInstanceList_t;
typedef CSI_base* (*CREATE_SI)(stSP_Params_t, eventDataMap_t, serviceParams_t);
typedef std::map<std::string, HINSTANCE> extnSiObjList_t;
struct AppBloc_Elements {
APPBLOCNAME      m_acAppBlocName;
CFBNwExecutor   *ptrFbnExec;
};

typedef std::map<APPLET_ID, AppBloc_Elements> AppBlocElemList;
typedef std::map<APPBLOCNAME, APPLET_ID> AppBlocsList;

如果我在大多数用例中使用标准 STL 库,我的整个应用程序包含 50 多个 cpp 文件。

【问题讨论】:

  • “特别是在我使用 STL 库和 new 运算符的地方”——这是你的问题,不需要使用 new 为 STL 容器实例分配空间。
  • 不再有 STL 这样的东西,而不是您使用该术语的方式。它都被称为“标准库”。
  • 在清除它们之前不需要从标准容器中删除元素,除非它们包含拥有原始指针或其他原始拥有句柄。 tagsList_teventDataMap_t 没有任何原始拥有指针,您不必担心这些类型的容器泄漏。
  • 您显示的代码中没有任何内容看起来像内存泄漏。如果您有,则需要提供minimal reproducible example 以获得帮助。

标签: c++ c++11 memory memory-leaks stl


【解决方案1】:

回答您的问题“我每次访问 STL 库时都会造成内存泄漏吗?”是。标准库容器确实会根据需要为其操作分配内存,但您必须非常努力地使它们真正泄漏它们自己的内存。您提供的 VLD 输出显示了 unordered_map 分配的内存;它没有泄漏,该地图使用它来存储您的数据。如果你清除地图,这块内存将被释放。

您的内存使用量上升的原因有两种。首先,您可能会无限期地将越来越多的数据推送到某个容器中。例如。您可能正在创建新事件并将它们放入 sm_eventDataMapList 而不删除旧事件。在这种情况下,无论是标准容器还是您的手卷结构,内存都必须增加。使用 VLD 很容易发现这一点,因为随着时间的推移,它会在某个特定位置显示越来越多的分配。这不是容器泄漏,这是您的代码中的错误。

第二种可能性,这可能就是发生在你身上的事情——你正在泄漏由new 分配的内存。 您的有责任删除所有使用new 分配的对象,无论它们是否不再使用。容器不会自动执行此操作。智能指针可以帮助您完成这项任务,但我建议仔细查看您使用 new 的所有地方,并考虑您是否真的需要以这种方式分配对象。通常你可以用不同的方式做事,得到更简单、更安全的代码。

例如在这种情况下:unordered_map&lt;CSI_base *, stSP_Params_t &gt; - 使用指向对象的指针作为键。从地图中删除该元素后,您是 deleteing 还是以其他方式处置该对象?

另一个例子:

struct stEventParams_t {
    EVENT_UID  m_unEventID;
    uint8_t    *m_ptrMemAddr;
    size_t     m_dataSize;
} ;

我将假设这个结构拥有m_ptrMemAddr 指向的内存,而m_dataSize 是该内存块的大小。在这种情况下,每次删除stEventParams_t实例时都需要记住delete[]这个内存,这很容易忘记。

更好的方法 - 使结构移除拥有的资源:

struct stEventParams_t {
    ~stEventParams_t() {delete[] m_ptrMemAddr;}
    EVENT_UID  m_unEventID;
    uint8_t    *m_ptrMemAddr;
    size_t     m_dataSize;
} ;

更好 - 为此使用智能指针:

struct stEventParams_t {
    EVENT_UID  m_unEventID;
    std::unique_ptr<uint8_t[]> m_ptrMemAddr;
    size_t     m_dataSize;
} ;

甚至更好 - 使用适当的标准容器(不能泄漏内存,也不需要单独的size,因为容器知道它的长度):

struct stEventParams_t {
    EVENT_UID  m_unEventID;
    std::vector<uint8_t> m_data;
} ;

【讨论】:

  • 感谢您的详细说明。对于 CSI_base *,我真正关心的是它是一个外部共享库。我需要创建 CSI_base 对象并调用初始化函数。完成此操作后,我需要调用共享库中的发送/接收函数。因为我需要创建几个这样的对象,所以我将该对象指针存储在 unordered_map 中。如果我在初始化后删除 CSI_base *,我将无法将其用于发送/接收。例如,我将创建 20 个这样的对象。这种频繁的发送/接收调用会对内存产生任何影响吗?
猜你喜欢
  • 1970-01-01
  • 2020-05-24
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 2013-08-04
  • 2011-08-14
  • 1970-01-01
相关资源
最近更新 更多