Как правильно заменить глобальные операторы new и delete

Во-первых, на SO было как минимум 4-5 тем с похожей темой. Я читаю каждую из них и не чувствую, что они действительно помогают мне в этом конкретном вопросе. Если кто-то еще найдет повторяющийся вопрос, прошу прощения. Я проделал свою долю поиска, прежде чем опубликовать это, так как это кажется очень распространенным вопросом.

Я использую Visual Studio .NET 2003 в Windows 7.

У меня есть собственные перегрузки new / delete, которые указывают на мои собственные вызовы malloc () и free () для диагностики. Мои новые / удаляемые перегрузки находятся в файле заголовка, который я включил в несколько файлов.

Проблема в том, что кодовая база представляет собой спагетти, и нет простого способа убедиться, что эти перегрузки используются всем. Есть включения в сторонние библиотеки, которые являются черным ящиком. Мы также везде используем STL.

В своих тестах я обнаружил, что STL все еще смешивает вызовы моих собственных вызовов new / delete и стандартных вызовов new / delete MSVC.

Включение моего файла заголовка в тысячи других файлов кажется нереальным, это займет слишком много времени. Может ли кто-нибудь предложить несколько советов о том, как правильно и эффективно перегрузить новое / удалить глобально, чтобы все использовали мой настраиваемый диспетчер памяти?


person void.pointer    schedule 18.11.2011    source источник
comment
Если вы определяете операторы глобально в предварительно скомпилированном заголовке, это должно охватывать большую часть территории. В качестве альтернативы вы можете использовать функции кучи CRT, если это необходимо для обнаружения утечек памяти.   -  person AJG85    schedule 18.11.2011


Ответы (2)


Это не так. Вы заменяете оба оператора, и это делается во время ссылки. Все, что вам нужно сделать, это написать единый TU, который определяет эти операторы, и связать его с миксом. Больше никому не нужно об этом знать:

// optional_ops.cpp

void * operator new(std::size_t n) throw(std::bad_alloc)
{
  //...
}
void operator delete(void * p) throw()
{
  //...
}

В принципе, нет необходимости в каких-либо файлах заголовков для объявления этих функций (operator new, operator delete), поскольку объявления этих двух функций уже жестко запрограммированы в языке, если хотите. Однако имена std, std::bad_alloc и std::size_t не заранее объявлены, поэтому вы, вероятно, захотите включить <new> или какой-либо другой заголовок для предоставления этих имен.

В C ++ 11 и более поздних версиях вы также можете использовать decltype(sizeof(0)) для получения размера первого параметра способом, не требующим какой-либо библиотеки. C ++ 11 также имеет более простую модель исключений без спецификаций динамических исключений (которые были окончательно удалены из языка полностью в C ++ 17).

void * operator new(decltype(sizeof(0)) n) noexcept(false)
{
  //...
}
person Kerrek SB    schedule 18.11.2011
comment
Не будет ли компоновщик жаловаться на повторяющиеся определения? Я думаю, что здесь применяется ODR. Не говоря уже о том, что у нас есть 120 DLL, которые мы создаем, и мне пришлось бы связывать их в каждом из этих проектов DLL. Думаю, это все же лучше, чем альтернативы. - person void.pointer; 18.11.2011
comment
@RobertDailey: Нет, особый случай, охватываемый стандартом, слабые ссылки и т. Д. Я на самом деле сообщил об ошибке в GCC, касающейся этого на днях, поэтому с последней версией это должно работать даже с -fwhole-program и -flto и еще много чего (см. здесь и здесь.)` - person Kerrek SB; 18.11.2011
comment
Не могли бы вы объяснить, что вы имеете в виду под стандартными и слабыми ссылками? Спасибо!! - person void.pointer; 18.11.2011
comment
@RobertDailey: Стандарт предусматривает, что определения пользователей заменяют функции, поэтому нет нарушения ODR. Компилятор реализует это, делая стандартные функции слабыми ссылками, которые переопределяются компоновщиком, если найден другой символ с тем же именем. - person Kerrek SB; 18.11.2011
comment
Спасибо за информацию. В каком разделе стандарта это можно найти? Я хочу еще немного прочитать об этом и связанных с ним правилах. Большое спасибо!! - person void.pointer; 18.11.2011
comment
@KerrekSB, не забудьте еще 3 версии: new [], delete [], nothrow - person MSN; 18.11.2011
comment
@MSN, скорее, power-3: throw / nothrow, скаляр / массив, новое / удаление. Ага. Множество кода :-) Но, по крайней мере, вы ничего не сломаете, если всегда будете держать каждую новую / удаляемую пару вместе. - person Kerrek SB; 18.11.2011
comment
Работает ли этот же метод для замены malloc () и free () в Windows? - person void.pointer; 18.11.2011
comment
@RobertDailey: malloc() живет на другом уровне. Обычно malloc() фактически используется непосредственно функциями распределения C ++. Поскольку malloc() является частью вашей библиотеки C, вы должны убедить компоновщик предпочесть другую реализацию. В Linux это тривиально с LD_PRELOAD, и здесь несложно попробовать любой из конкурирующих распределителей (tcmalloc - это довольно драматично). - person Kerrek SB; 18.11.2011
comment
Да, в Windows вы не можете предложить замену новых / удаленных функций для всей программы, не связав их с каждой DLL. Или, по крайней мере, заставить библиотеки импортировать эти функции, но я не уверен, что это сработает. Мне жаль, что библиотеки DLL работают таким образом. На других платформах, когда разделяемые библиотеки связаны во время запуска программы, они обычно подчиняются тем же правилам, что и статически связанные единицы перевода, поэтому, если ваша программа заменяет новые / удаляет разделяемые библиотеки, они также получат эти замены. - person bames53; 18.11.2011
comment
Нужно ли их определять в cpp? Разве они не могут быть встроены? - person Abhishek Jain; 31.07.2015
comment
@AbhishekJain: Поскольку они влияют на всю программу, нет, они, как правило, не могут быть встроены. Линкерам необходимо предоставить некоторую особую магию (слабую связь), чтобы разрешить этот механизм замены. - person Kerrek SB; 31.07.2015
comment
Вы также можете переопределить версии new без выброса, которые имеют второй параметр const std::nothrow_t& (часть стандарта C ++) - person rustyx; 02.10.2015
comment
Есть ли какая-то документация по всем новым / удаленным, которые могут / должны быть реализованы? бросает, не бросает, массивы и т. д. редактировать: это возможно; en.cppreference.com/w/cpp/memory/new/operator_new - person Soylent Graham; 25.02.2016
comment
@SoylentGraham: Самый простой способ - прочитать раздел [support.dynamic] в Стандарте. Это дает исчерпывающее описание. - person Kerrek SB; 25.02.2016
comment
Когда я впервые попытался заменить new / delete, процесс упал, прежде чем он мог прерваться на _start () ... или так было сказано в сеансе отладки. Процесс прошел без проблем после того, как я добавил #include ‹new› и удалил // # include ‹iostream› // с использованием пространства имен std; Примечание: это на мишени arm v5. - person stephen; 16.11.2017
comment
@stephen: имена std и std::size_t не объявлены заранее, поэтому вы должны объявить их в TU, где вы определяете функции замены; обычно через #include <new>. Но пользователям не нужно включать этот заголовок. - person Kerrek SB; 16.11.2017
comment
@KerrekSB Именно так. Что меня интересует, так это то, что программа скомпилирована и сразу же ушла в сорняк до моей модификации. Это как если бы std или ‹iostream› разрешили какой-то несоответствующий код. - person stephen; 16.11.2017
comment
Работает ли это решение с разделяемыми библиотеками под Linux? - person Andreas Pasternak; 08.08.2018
comment
@AndreasPasternak: Вы имеете в виду разделяемые библиотеки, загружаемые во время выполнения через dlopen? Я не уверен, но думаю, что да, по крайней мере, до тех пор, пока разделяемая библиотека сама не заменяет операторов. Встроенные определения обычно реализуются со слабой связью, и я полагаю, что это продолжает работать во время динамической загрузки. Но лучше всего попробовать самому! - person Kerrek SB; 08.08.2018
comment
@KerrekSB: Нет, я просто связываю все вместе во время компиляции. К сожалению, это не работает с разделяемыми библиотеками, если я не использую LD_PRELOAD. См. Мой вопрос здесь: stackoverflow.com/questions/51735795/ - person Andreas Pasternak; 08.08.2018
comment
@AndreasPasternak: Итак, вы связываете все за один шаг, и одна из ваших библиотек заменяет операторы, а замененные функции в конечном итоге не вызываются? Или они вызываются только кодом из библиотеки, которая их заменяет? Я не уверен, я бы поискал слабые связи в документации вашего компилятора. - person Kerrek SB; 08.08.2018
comment
@KerrekSB: я создаю одну общую библиотеку, которая содержит перегруженные операторы new / delete. Затем я создаю еще одну простую общую библиотеку. Затем я связываю все вместе с исполняемым файлом. новые / удаления в исполняемом cpp обрабатываются правильно, а новые / удаления в разделяемой библиотеке - нет. Если я предварительно загружу свою небольшую общую библиотеку, которая содержит new / delete с LD_PRELOAD, она будет работать тяжело, а new / delete в другой общей библиотеке будет обрабатываться должным образом. Проверю слабую связь, спасибо - person Andreas Pasternak; 08.08.2018

Также добавьте эти строки:

void *operator new[](std::size_t s) throw(std::bad_alloc)
{
    // TODO: implement
    return NULL;
}
void operator delete[](void *p) throw()
{
    // TODO: implement
}
person Extrunder    schedule 18.11.2011