У меня есть некоторый код, который отказывает в большой системе. Однако код по существу сводится к следующему псевдокоду. Я удалил большую часть детали, поскольку я попытался свести это к скелету; я не думаю, что это пропускает что-либо решающее все же.
// in a DLL:
#ifdef _DLL
#define DLLEXP __declspec(dllexport)
#else
#define DLLEXP __declspec(dllimport)
#endif
class DLLEXP MyClass // base class; virtual
{
public:
MyClass() {};
virtual ~MyClass() {};
some_method () = 0; // pure virtual
// no member data
};
class DLLEXP MyClassImp : public MyClass
{
public:
MyClassImp( some_parameters )
{
// some assignments...
}
virtual ~MyClassImp() {};
private:
// some member data...
};
и:
// in the EXE:
MyClassImp* myObj = new MyClassImp ( some_arguments ); // scalar new
// ... and literally next (as part of my cutting-down)...
delete myObj; // scalar delete
Обратите внимание, что соответствие новому скаляру и скаляру удаляет, используются.
В Отладочная сборка в Visual Studio (2008 Pro), в Microsoft
_ASSERTE(_CrtIsValidHeapPointer(pUserData));
Около вершины стека следующие объекты:
mydll_d.dll!operator delete()
mydll_d.dll!MyClassImp::`vector deleting destructor'()
Я думаю, что это должно быть
mydll_d.dll!MyClassImp::`scalar deleting destructor'()
Таким образом, программа ведет себя, как будто я записал
MyClassImp* myObj = new MyClassImp ( some_arguments );
delete[] newObj; // array delete
Адрес в pUserData
тот из myObj
самостоятельно (в противоположность участнику). Память вокруг того адреса похожа на это:
... FD FD FD FD
(address here)
VV VV VV VV MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
FD FD FD FD AB AB AB AB AB AB AB AB EE FE EE FE
...
где четыре VV
s являются, по-видимому, адресом таблицы виртуальной функции, MM...MM
опознаваемые членские данные, и другие байты являются различными специальными маркерами, помещенными на месте отладчиком (например, FD FD
s являются 'защитными байтами' вокруг устройства хранения данных объекта).
Незадолго до отказа утверждения я действительно вижу VV
s изменение, и задаются вопросом, происходит ли это из-за переключателя к таблице виртуальной функции базового класса.
Я знаю о проблеме неправильного уровня в разрушении перенесения иерархии классов. Это не проблема здесь; мои деструкторы являются все виртуальными.
Я отмечаю страницу "BUG: Wrong Operator Delete Called for Exported Class" Microsoft http://support.microsoft.com/kb/122675, но это, кажется, расценивает неправильный исполняемый файл (с неправильной "кучей") пытающийся взять на себя ответственность за разрушение данных.
В моем случае случается так, что неправильный 'аромат' удаления деструктора, кажется, применяется: т.е. вектор, а не скаляр.
Я нахожусь в процессе попытки произвести минимальный код сокращения, который все еще показывает проблему.
Однако любые подсказки или подсказки для помощи с тем, как исследовать эту проблему далее, очень ценились бы.
Возможно, самая большая подсказка здесь mydll_d.dll!operator delete()
на стеке. Если я ожидаю, что это будет myexe_d.exe!operator delete()
, указание, что DLLEXP
s были 'потеряны'?
Я предполагаю, что это могло быть экземпляром двойного - удаляют (но я не думаю так).
Есть ли хорошая ссылка, которую я могу считать относительно какой _CrtIsValidHeapPointer
проверки на?
Похоже, это может быть проблема выделения одной кучи и попытки удаления на другой. Это может быть проблемой при выделении объектов из библиотеки DLL, поскольку у библиотеки есть собственная куча. Из кода, который вы показываете, не похоже, что это будет проблемой, но, может быть, при упрощении что-то было потеряно? Раньше я видел, как этот код использует фабричные функции и виртуальные методы destroy
для объектов, чтобы гарантировать, что выделение и удаление происходит в коде dll.
Microsoft предоставляет исходный код для их среды выполнения C; вы можете проверить там, что делает _CrtIsValidHeapPointer
. В моей установке он находится в C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ crt \ src \ dbgheap.c
.
Еще одно предложение - проверить дизассемблирование
delete newObj; // scalar delete
и сравнить его с дизассемблированием, созданным для
delete[] newObj;
и
delete pointerToClassLikeMyClassThatIsInExeAndNotDll;
, чтобы проверить вашу теорию о вызове delete []
. Точно так же вы можете проверить стек вызовов для
delete pointerToClassLikeMyClassThatIsInExeAndNotDll;
, чтобы проверить свою теорию о mydll_d.dll! Operator delete ()
по сравнению с myexe_d.exe! Operator delete ()
.
Спасибо за все ответы и комментарии. Все они были полезны и актуальны.
Любая дополнительная информация по-прежнему приветствуется.
Следующий комментарий на мой вопрос от Hans Passant:
Как только вы начинаете экспортировать классы из DLL, компиляция с /MD становится очень важной. По-моему, это похоже на /MT.
В результате этого я внимательнее присмотрелся к настройке связей во всем проекте. Я обнаружил "похороненные" экземпляры /MT и /MTd, которые должны были быть /MD и /MDd, плюс некоторые связанные с этим несоответствия в других настройках.
Исправив их, утверждение теперь не выбрасывается, и код, похоже, ведет себя правильно.
Вот некоторые вещи, которые следует проверить при возникновении сбоев или отказов утверждений при выходе из диапазонов выполнения и вызове деструкторов. Убедитесь, что во всех проектах (включая зависимости) и во всех конфигурациях (особенно в проблемной):
(Здесь пути *.vcproj относительны к .)
Интересно, что скалярный "аромат" удаления, который я ожидал, все еще не вызывается (точка останова не достигается). То есть, я по-прежнему вижу только векторный деструктор удаления. Поэтому, возможно, это была "красная селедка".
Возможно, это просто проблема реализации Microsoft, или, возможно, есть еще какая-то тонкость, которую я упустил.