Утечки памяти после использования typeinfo::name()

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

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

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

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


person icabod    schedule 29.11.2011    source источник
comment
Возможно актуально? http://connect.microsoft.com/VisualStudio/feedback/details/106937/memory-leaks-reported-by-debug-crt-inside-typeinfo-name. Какой компилятор вы используете? Может попробовать другой компилятор, если это возможно?   -  person Chris Parton    schedule 29.11.2011
comment
Поскольку он кэшируется, не беспокойтесь об этом.   -  person Jagannath    schedule 29.11.2011
comment
@jagansai: Меня не беспокоит сама утечка, так как она влияет только на вывод отладчика при выходе из приложения — меня беспокоит то, что она может скрыть настоящие утечки памяти. И выглядит неряшливо. Мне нравится аккуратный вывод отладчика :)   -  person icabod    schedule 29.11.2011
comment
@ChrisParton: Спасибо за эту ссылку - не заметил этого при гуглении (и я думал, что я хороший гуглер). Я использую VC++2008, в котором есть ошибка, и в данный момент я не могу перейти на что-то более новое. Я заметил, что есть обходной путь (удалить экземпляры typeinfo вручную?), который я вскоре опробую.   -  person icabod    schedule 29.11.2011
comment
@icabod: Не беспокойся, дай мне знать, как дела.   -  person Chris Parton    schedule 29.11.2011
comment
@ChrisParton: я добавил ответ, который обобщает информацию по предоставленной вами ссылке, но оставляю вопрос открытым на случай, если у кого-то еще есть какие-либо комментарии/идеи.   -  person icabod    schedule 29.11.2011
comment
Хорошая идея, надеюсь, кто-нибудь сможет придумать обходной путь. Спасибо за признание в вашем ответе, ценю это :)   -  person Chris Parton    schedule 29.11.2011
comment
Попробуйте мой обходной путь. У меня вроде работает)   -  person Stas    schedule 24.06.2012


Ответы (4)


Другое решение состоит в том, чтобы исправить основную проблему. На самом деле это не утечка памяти, а просто ложный отчет. Блокам памяти, выделенным для строк 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)
person MerkX    schedule 06.04.2013
comment
Изменил мой принятый ответ на этот, так как он немного чище (?), чем другие ответы, отчасти из-за того, что #if ограничивает его только затронутыми сборками. - person icabod; 08.04.2013

Я только что наткнулся на эту проблему, пытаясь очистить журнал 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;
   ...
}
person Stas    schedule 23.06.2012

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

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

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

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

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

person icabod    schedule 29.11.2011
comment
Принятие моего собственного ответа в отсутствие каких-либо других. - person icabod; 12.01.2012
comment
Да, у вас есть одна утечка, поскольку вы удалили память только для строки. Существует также соответствующий отдельный узел связанного списка, который необходимо удалить. Смотрите мой ответ. - person Stas; 24.06.2012
comment
Это действительно плохое решение, поскольку строка кэшируется в памяти, поэтому, если вы снова вызовете typeid(*this).name() после удаления, вы получите строку мусора. - person Ohad Horesh; 08.10.2013

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 Update 3.

person Aleksey Malov    schedule 06.09.2017