Переопределение распределителя памяти в MSVC++

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

Теперь у меня есть код, который может создавать (и записывать) обратные трассировки выделений, и я также использовал DUMA. Однако попытки заменить функции распределения не удались, когда мы использовали потоки, потому что streambuf вызывает некоторый вариант отладки и делает это непоследовательно между new и delete.

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

Примечание. В настоящее время мы застряли на Visual C++ 9.0 (Visual Studio 2008).

Изменить: вряд ли можно избежать распределителя отладки, поскольку стандартная библиотека C++ должна иметь согласованные определения new и delete между функциями и экземплярами, скомпилированными в библиотеку, и экземплярами, сгенерированными в пользовательском коде, поскольку выделение может быть выполнено одним и освобождение другим. Это, кстати, означает, что определение статических встроенных вариантов в принудительно включенном заголовке вряд ли поможет.

Edit2: это невозможно с динамической компоновкой, потому что Windows связывает символы из определенных библиотек DLL, поэтому нет возможности переопределить их во время компоновки. Но динамическое связывание нам не нужно и мы его не используем, потому что основной целью является WinCE, а статическое связывание там по умолчанию.


person Jan Hudec    schedule 10.10.2012    source источник
comment
Я не делал этого уже несколько лет, но мне кажется, что проблема в том, что DLL библиотеки времени выполнения содержит код operator new и operator delete; они жестко связаны с DLL, поэтому вы не можете их заменить. Хитрость заключается в том, чтобы связать статическую библиотеку времени выполнения и предоставить свои собственные operator new и operator delete, так что те, что в статической библиотеке, не нужны. Но это работает только для монолитного приложения; это доставит вам неприятности, если вы создадите свои собственные DLL. Добро пожаловать в ад DLL.   -  person Pete Becker    schedule 10.10.2012
comment
@PeteBecker: Наш проект ориентирован на мобильные системы (WinCE и некоторые другие операционные системы), а статическое связывание по умолчанию в WinCE, поэтому мы всегда связывались статически. Переопределение (с опцией компилятора /FORCE) в основном работало, но я помню, что у меня была проблема с streambuf, делающей что-то странное.   -  person Jan Hudec    schedule 11.10.2012
comment
WinCE хорошо назван. Когда я работал в Dinkumware, одной из моих задач было портирование стандартной библиотеки на WinCE. Это было десять лет назад, возможно, что-то изменилось. В то время у них был новый компоновщик, который выполнял компоновку в стиле Unix: в отличие от компоновщика DOS, он не сохранял имена, определенные в библиотеке; он разрешал имена, которые мог, затем переходил к следующей библиотеке, он делал столько проходов по списку библиотек, сколько требовалось для разрешения имен. Я провел недели, запуская подробные ссылки и глядя на результаты, чтобы понять, почему это смешивает библиотеки. Также много звонков в службу поддержки.   -  person Pete Becker    schedule 11.10.2012
comment
Упс, из-за ограничений на количество символов я пропустил самую важную часть: компоновщик иногда менял порядок библиотек на последующих проходах, а иногда создавал неправильные имена путей. Я предполагаю, что эти проблемы были устранены к настоящему времени.   -  person Pete Becker    schedule 11.10.2012


Ответы (1)


Вот как мы это делаем (с jemalloc, но можно и с любым другим аллокатором):

  1. Скомпилируйте пользовательский распределитель памяти отдельно как статическую библиотеку C.
  2. Свяжите свое приложение C++ с пользовательской библиотекой распределителя.
  3. Переопределите операторы new и delete в приложении C++ для вызова пользовательского распределителя.

Примечания:

  • Пользовательский распределитель должен быть написан на C, а не на C++.
  • Невозможно обеспечить достаточно раннюю инициализацию распределителя, если только он не находится в отдельной библиотеке.
  • Переопределение malloc и free также возможно, но намного сложнее в MSVC, потому что они не являются "слабо" связанными, и потому что в MSVC есть много пользовательских вариантов (например, с использованием флага компоновщика /FORCE:MULTIPLE).

Образец кода:

void* operator new(size_t size)
{
  void* ptr = my_malloc(size);
  if (ptr)
    return ptr;
  else
    throw std::bad_alloc();
}

void* operator new[](size_t size)
{
  void* ptr = my_malloc(size);
  if (ptr)
    return ptr;
  else
    throw std::bad_alloc();
}

void* operator new(size_t size, const std::nothrow_t&) throw()
{
  return my_malloc(size);
}

void* operator new[](size_t size, const std::nothrow_t&) throw()
{
  return my_malloc(size);
}

void operator delete(void* pointer) throw()
{
  my_free(pointer);
}

void operator delete[](void* pointer) throw()
{
  my_free(pointer);
}

void operator delete(void* pointer, const std::nothrow_t&) throw()
{
  my_free(pointer);
}

void operator delete[](void* pointer, const std::nothrow_t&) throw()
{
  my_free(pointer);
}
person rustyx    schedule 09.12.2016
comment
Где вы переопределяете operator new и operator delete? Как убедиться, что они доступны для всех экземпляров std::allocator? Имейте в виду, что собственный код приложения, в соответствии с хорошей практикой C++, не содержит никакого использования new, кроме как через стандартную библиотеку или интерфейсы Boost. И как вы справляетесь с этим надоедливым несоответствием в std::basic_streambuf (прошло 6 лет, так что моя память несколько заржавела в отношении того, что именно он делал, но это была какая-то функция отладки в одном направлении в сочетании со стандартной в другом)? - person Jan Hudec; 09.12.2016
comment
вау, этот подход отлично работает! с его помощью мне удалось заменить распределитель памяти C++ по умолчанию на jemalloc в моем приложении. просто поместите примеры кодов в файл заголовка, убедитесь, что заголовок включен в каждый cpp, требующий пользовательского распределителя памяти, и свяжите его с библиотекой пользовательского распределителя памяти, после чего все готово. хотя было бы неплохо, если бы мы могли получить пользовательский Распределитель памяти включен во время компоновки, просто свяжите tcmalloc, однако я бы сказал, что представленный здесь подход — почти лучший, который мы могли бы получить на данный момент, конечно, при условии, что у вас есть доступ к исходному коду. - person uwydoc; 06.02.2018