Утечки памяти после использования typeinfo :: name () - PullRequest
8 голосов
/ 29 ноября 2011

У меня есть программа, в которой, частично для информационного ведения журнала, я выводю имена некоторых классов по мере их использования (в частности, я добавляю запись в журнал, повторяя 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* в строку с нулевым символом в конце, представляющую удобочитаемое имя типа.Указанная память кэшируется и никогда не должна быть освобождена напрямую.

Однако, когда я выхожу из программы в отладчике, любое «новое» использование typeinfo::name() обнаруживается как утечка памяти.Если я выведу информацию для 2 классов, я получу 2 утечки памяти и так далее.Это намекает на то, что кэшированные данные никогда не освобождаются.

Хотя это не является серьезной проблемой, это выглядит грязно, и после длительного сеанса отладки может легко скрыть подлинные утечки памяти.

IЯ посмотрел вокруг и нашел некоторую полезную информацию (один ответ SO дает интересную информацию о , как typeinfo может быть реализована ), но мне интересно, должна ли эта память обычно освобождаться системой или естьчто-то, что я могу сделать, чтобы «не заметить» утечки при отладке.

У меня есть план резервного копирования, который заключается в том, чтобы самому кодировать метод getMessageName и не полагаться на typeinfo::name, но я 'В любом случае, я хотел бы знать, что я что-то пропустил.

Ответы [ 4 ]

4 голосов
/ 24 июня 2012

Я только что наткнулся на эту проблему, пытаясь очистить журнал VLD .Да, это известная ошибка , которая исправлена ​​только в 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;
   ...
}
4 голосов
/ 06 апреля 2013

Другое решение - исправить основную проблему. На самом деле это не утечка памяти, а просто ложный отчет. Блоки памяти, выделенные для tyepinfo () и строки name (), имеют неправильный тип блока. Вероятно, не очень хорошая идея «освобождать» эту память, поскольку ЭЛТ предпримет попытку освободить ее снова. Хорошей новостью является то, что это было окончательно исправлено в VS2012 (_MSC_VER 1700 +).

Поскольку это применимо только к сборкам _DEBUG, следующее может быть более безопасным решением. Функция _FixTypeInfoBlockUse () должна вызываться, как указано выше, непосредственно перед выходом из точки входа модуля (main, WinMain и т.

#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)
1 голос
/ 29 ноября 2011

Как отметил Крис Партон в комментариях, это, похоже, известная ошибка , по крайней мере, с той версией компилятора, которую я использую - обновление до VC11 исправит проблему, если я смогу обновить.

Попытка удалить вывод typeinfo::name() частично работает:

std::string getMessageName(void) const
{
    std::string typeStr(typeid(*this).name());
    delete (typeid(*this).name());
    return typeStr;
}

Однако все еще есть некоторые утечки памяти - я только что заметил, что раньше у меня возникали две утечки за вызов (возможно, из-за того, что классы были внутри пространства имен?). Используя вышеприведенную версию кода, это привело к одной утечке за вызов.

Другое решение, которое, похоже, работает, - это связать динамическую версию библиотек MFC (да, я использую MFC, не судите меня), а не статическую версию.

0 голосов
/ 06 сентября 2017

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 не сообщает об утечках памяти в type_info :: name () в VS2015, обновление 3.

...