我有一个程序,其中,部分用于信息日志记录,我输出一些类的名称(特别是我在Messages::CSomeClass transmitted to 127.0.0.1
的行中添加一个条目)。我使用类似于以下代码执行此操作:
std::string getMessageName(void) const {
return std::string(typeid(*this).name());
}
是的,在有人指出之前,我意识到typeinfo::name
的输出是特定于实现的。
根据MSDN
type_info::name
成员函数将const char*
返回到以null结尾的字符串,表示该类型的可读名称。指向的内存是缓存的,不应该直接取消分配。
但是,当我在调试器中退出程序时,typeinfo::name()
的任何“新”用法都会显示为内存泄漏。如果我输出2个类的信息,我会得到2个内存泄漏,依此类推。这暗示永远不会释放缓存的数据。
虽然这不是一个主要问题,但它看起来很混乱,经过长时间的调试后,它可以很容易地隐藏真正的内存泄漏。
我环顾四周并找到了一些有用的信息(一个SO答案提供了一些关于how typeinfo may be implemented的有趣信息),但我想知道这个记忆是否应该被系统释放,或者是否有什么东西我调试时可以做“不注意”泄漏。
我确实有一个备用计划,即自己编写getMessageName
方法代码并且不依赖typeinfo::name
,但无论如何我想知道是否有我错过的内容。
答案 0 :(得分:4)
我刚刚在试图清除VLD的日志时偶然发现了这个问题。是的,这是known bug,仅在VC11中修复。它存在于MSVC的早期版本(包括2010)中。仅当您使用MFC时才会出现此错误。如果使用MFC作为DLL而不是静态库,则内存泄漏仍然存在,但不会被检测到。
有type_info
个名称的全局缓存,但未清除(摘录自<typeinfo>
):
struct __type_info_node {
void *_MemPtr;
__type_info_node* _Next;
};
extern __type_info_node __type_info_root_node;
想法是清除此缓存。这个功能对我有用:
#include <typeinfo>
void clear_type_info_cache()
{
__type_info_node* & node = __type_info_root_node._Next;
while(node)
{
if (node->_MemPtr)
{
delete node->_MemPtr;
}
__type_info_node* tempNode = node;
node = node->_Next;
delete tempNode;
}
}
退出前请致电clear_type_info_cache()
。您可以使用atexit
#include <cstdlib>
int WinMain(...)
{
atexit(&clear_type_info_cache);
...
}
或在离开WinMain之前立即致电
struct dummy_scope_exit
{
typedef void (*Fun)();
dummy_scope_exit(Fun f) : m_f(f) {}
~dummy_scope_exit() { m_f(); }
Fun m_f;
};
int WinMain(...)
{
dummy_scope_exit cleaner = &clear_type_info_cache;
...
}
答案 1 :(得分:4)
另一种解决方案是纠正潜在的问题。这不是真正的内存泄漏,只是一个错误的报告。分配给tyepinfo()和name()字符串的内存块被分配了错误的块类型。 “释放”这种记忆可能不是一个好主意,因为CRT会再次试图释放它。好消息是它最终在VS2012中修复(_MSC_VER 1700 +)。
由于这仅适用于_DEBUG版本,因此以下内容可能是更安全的解决方案。在退出模块入口点(main,WinMain等)之前,应该如上所述调用函数_FixTypeInfoBlockUse()。
#if defined(_DEBUG) && (_MSC_VER >= 1000 && _MSC_VER <= 1699)
//
// Debug memory block header:
// o Borrowed from the Microsoft CRT to fix the false "memory leak" report
// when using typeinfo 'name' accessor in a _DEBUG build of the library.
//
struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
#ifdef _WIN64
int nBlockUse;
size_t nDataSize;
#else
size_t nDataSize;
int nBlockUse;
#endif
long lRequest;
unsigned char gap[4];
};
static void __cdecl _FixTypeInfoBlockUse(void)
{
__type_info_node* pNode = __type_info_root_node._Next;
while(pNode != NULL)
{
__type_info_node* pNext = pNode->_Next;
(((_CrtMemBlockHeader*)pNode) - 1)->nBlockUse = _CRT_BLOCK;
if (pNode->_MemPtr != NULL)
(((_CrtMemBlockHeader*)pNode->_MemPtr) - 1)->nBlockUse = _CRT_BLOCK;
pNode = pNext;
}
}
#endif//defined(_DEBUG) && (_MSC_VER >= 1000 && _MSC_VER <= 1699)
答案 2 :(得分:1)
正如Chris Parton在评论中指出的那样,这似乎是known bug,至少对于我正在使用的编译器版本 - 升级到VC11会纠正问题,如果我能够升级的话。
尝试删除typeinfo::name()
的输出部分有效:
std::string getMessageName(void) const
{
std::string typeStr(typeid(*this).name());
delete (typeid(*this).name());
return typeStr;
}
然而,仍然存在一些内存泄漏 - 我只是注意到以前我似乎每次调用都会出现两次泄漏(可能是由于类在命名空间内?)。使用上面的代码版本,每个调用都会发生一次泄漏。
另一种似乎有用的解决方案是链接MFC库的动态版本(是的,我使用的是MFC,不要评判我),而不是静态版本。
答案 3 :(得分:0)
VS在单链表中存储类型信息。此列表的标题可通过名称 __ type_info_root_node 访问的不透明结构访问。实际上它是一个SLIST_HEADER结构。
Win32 API具有一组并发安全功能,可以使用这种结构。 修复内存泄漏报告在您的情况下,您需要删除此列表中的所有节点。
#include <Windows.h>
#include <typeinfo>
#include <vld.h>
void ClearTypeinfoCache()
{
#ifdef _DEBUG
while (auto entry = InterlockedPopEntrySList(reinterpret_cast<PSLIST_HEADER>(&__type_info_root_node)))
{
free(entry);
}
#endif
}
int main()
{
atexit(ClearTypeinfoCache);
return 0;
}
更新:VLD 2.5.1不报告VS2015 Update 3中type_info :: name()的内存泄漏。