#c #templates #memory #allocation
#c #шаблоны #память #распределение
Вопрос:
Недавно я столкнулся с проблемами с выделением памяти, выполненным в одной DLL (или *.so — portable code), и освобождением, выполненным в другой DLL. Ошибки, с которыми я столкнулся до сих пор, являются:
- Это просто не работает — сбой assert () при отладке.
- Это не сработает, если одна DLL была статически связана со стандартной библиотекой C, а другая DLL была динамически связана с ней.
- Это не сработает, если одна DLL выполняет выделение, затем DLL выгружается, а другая DLL пытается освободить эту память.
В принципе, правило, которому я решил следовать, заключается в том, чтобы не создавать выделения в одной DLL и не выпускать ее в другой (и предпочтительно хранить ее в одном cpp-файле). Обычно это также означает, что я не должен выполнять выделение в заголовочном файле, который может совместно использоваться более чем одной библиотекой DLL. Это означает, что я не должен выполнять выделения во временных файлах (поскольку все они находятся в заголовке), и это довольно большое ограничение.
Когда мне действительно нужно создать новый объект в шаблоне, то, что я делаю сейчас, это выделяю для него в памяти cpp-файл и только затем запускаю его c’torс помощью оператора размещения new.
// header
class MyBase
{
public:
static void* allocate(std::size_t i_size);
};
template <typename T>
class MyClass: MyBase
{
public:
T* createT();
};
temlpate <typename T>
T* MyClass<T>::createT()
{
void* pMem = MyBase::allocate( sizeof(T) );
return new (pMem) T;
}
// Cpp file
void* MyBase::allocate(std::size_t i_size)
{
return malloc( i_size );
}
Хотя это работает, это немного некрасиво. Это означает написание шаблонного кода без использования new.
Другим следствием является то, что если вы не знаете, что шаблон был написан с использованием этой техники, вы должны использовать только его методы const в файле заголовка (включая другие шаблоны) (это предполагает, что методы const не выделяют и не освобождают память). Сюда входит STL. На самом деле, одно из мест, где я столкнулся с этим, было в векторе, размер которого был изменен одной динамической библиотекой (на HP-UX), а затем выгружен, чем его предшественник, вызванный другой динамической библиотекой.
Есть ли какое-то широко известное решение для этого, которого мне просто не хватает, или это просто упущенная из виду проблема?
Комментарии:
1. «Это не работает» — это НЕ описание ошибки.
2. @Tomalak Geret’kal: Отладочные версии завершаются ошибкой при выполнении assert(). Выпуск версии иногда завершается сбоем в SIGSEGV.
3. Правильно, поэтому опубликуйте обратную трассировку из SIGSEGV и текст ошибки утверждения.
4. проблема каким-то образом решается в boost::shared_ptrs; возможно, вам следует взглянуть на их реализацию
Ответ №1:
В принципе, правило, которому я решил следовать, заключается в том, чтобы не создавать выделения в одной DLL и не выпускать ее в другой (и предпочтительно хранить ее в одном cpp-файле). Обычно это также означает, что я не должен выполнять выделение в заголовочном файле, который может совместно использоваться более чем одной библиотекой DLL.
Нет, одно не подразумевает другое.
Если ваши функции выделения и отмены выделения являются шаблонами в заголовке, это все равно нормально; просто убедитесь, что вы ограничили использование этих функций для любого данного объекта одним TU1.
Инкапсулируйте ваши объекты таким образом, чтобы для кода в DLL 1 было недопустимо / запрещено / неопределенно вызывать эти функции для объектов из DLL 2. Оформите это контрактом с пользователем, напишите в комментариях, что право собственности на объекты остается за исходным контекстом выделения, затем переходите к следующей части вашего проекта, никогда больше не беспокоясь об этом.
То, что функции доступны для всех TU, не имеет значения; в конце концов, вы всегда можете попытаться delete
использовать эти вещи!
1 — единица перевода. Примерно эквивалентно одному предварительно обработанному .cpp
файлу.
Комментарии:
1. @Tomalak Geret’kal — можете ли вы продемонстрировать «Инкапсулируйте свои объекты таким образом, чтобы для кода в DLL 1 было недопустимо / запрещено / неопределенно вызывать эти функции для объектов из DLL 2»?
2. @selalerer: Прочитайте второе предложение того же абзаца.
3. @Tomalak Geret’kal — Есть ли такая документация у всех соответствующих классов в проектах, над которыми вы работаете? Или все люди в проекте знают, что это неявное ограничение для всех классов, если не указано иное?
4. @selalerer: Это задокументировано. Функции без такой документации имеют неоднозначное право собственности на объекты и являются подозрительными. Это те, на разгадку которых вам придется потратить время и, возможно, провести рефакторинг.
5. @Tomalak Geret’kal: Я думаю, что решение в документации немного проблематично. Я работаю в проекте, в котором было и остается много разработчиков, не все из них заслуживают достаточного доверия, чтобы я мог рассчитывать на то, что они прочитают комментарии, поймут их и будут следовать им в точности. Спасибо, что подтвердили, что проблема существует. Я думаю, что буду продолжать искать решение, с которым я буду чувствовать себя более комфортно.
Ответ №2:
Вы должны динамически связать обе библиотеки DLL с одной и той же динамической библиотекой CRT. CRT не будет выгружен до тех пор, пока не будут выгружены все библиотеки DLL, которые ссылаются на него, и когда используется одна и та же библиотека DLL CRT, тогда безопасно выделить память в одной и освободить ее в другой.
Комментарии:
1. Я думаю, что эта информация верна конкретно для Windows, но не обязательно для другой комбинации OS arch.