Почему деструктор удаления вектора вызывается в результате скалярного удаления? - PullRequest
11 голосов
/ 30 июля 2010

У меня есть код, который дает сбой в большой системе.Однако код по существу сводится к следующему псевдокоду.Я удалил большую часть деталей, поскольку я пытался свести это к голым костям;Я не думаю, что это пропускает что-то решающее, хотя.

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

Мне известно о проблеме неправильного уровня в иерархии классов, подвергающейся разрушению.Это не проблема здесь;все мои деструкторы являются виртуальными.

Я отмечаю страницу Microsoft "BUG: неверное удаление оператора, вызванное для экспортируемого класса" http://support.microsoft.com/kb/122675, но это, похоже, связано с неверным исполняемым файлом (с неправильной кучей)взять на себя ответственность за уничтожение данных.

В моем случае это то, что применяется неправильный «аромат» удаления деструктора: т.е. вектор, а не скаляр.

Я нахожусь впроцесс создания минимального сокращенного кода, который все еще демонстрирует проблему.

Тем не менее, любые советы или подсказки, которые помогут в дальнейшем расследовании этой проблемы, будут высоко оценены.

Возможно,самая большая подсказка здесь - это mydll_d.dll!operator delete() в стеке.Должен ли я ожидать, что это будет myexe_d.exe!operator delete(), что означает, что DLLEXP s были «потеряны»?

Я полагаю, это может быть случай двойного удаления (но я так не думаю).

Могу ли я прочитать хорошую справку о том, что проверяет _CrtIsValidHeapPointer?

Ответы [ 5 ]

8 голосов
/ 30 июля 2010

Похоже, это может быть проблемой выделения одной кучи и попытки удаления в другой.Это может быть проблемой при выделении объектов из dll, поскольку dll имеет свою собственную кучу.Из кода, который вы показываете, не похоже, что это будет проблемой, но, возможно, в упрощении что-то было потеряно?В прошлом я видел подобный код, использующий фабричные функции и виртуальные destroy методы для объектов, чтобы убедиться, что распределение и удаление происходит в коде dll.

1 голос
/ 22 января 2011

Это поведение является особенным для MSVC 9, где оператор удаления для экспортируемого класса, который имеет виртуальный деструктор, неявно генерируется и переносится в вектор dtor со связанным флагом, где 1 означает (скаляр) и 3 означает (вектор) .

Истинная проблема этой вещи заключается в том, что она нарушает каноническую форму new / delete, где клиентский кодер не может отключить оператор удаления вектора в своем коде, если он считает, что это плохая идея используйте это.

Более того, вектор dtor, по-видимому, также выполняется неправильно, если new выделен в другом модуле, чем модуль, в котором находится реализация, и затем удерживается в статической переменной с помощью счетчика ссылок, который выполняет delete this (здесь вектор dtor вступает в игру) при завершении процесса.

Это соответствует проблеме кучи «bshields», уже упомянутой ранее, dtor выполняется в неправильной куче, и код выдает либо «не может прочитать эту область памяти», либо «нарушение доступа» при завершении работы - такие проблемы, кажется, очень часто.

Единственный способ обойти эту ошибку - запретить использование виртуального деструктора и запускать его самостоятельно, принудительно используя функцию delete_this из базового класса - глупо, поскольку вы подражаете вещам, которые должен делать виртуальный dtor сделать для тебя. Затем также выполняется скалярный dtor, и при отключении любые объекты с подсчетом, совместно используемые модулями, могут быть созданы безопасным способом, поскольку куча всегда правильно адресована исходному модулю.

Чтобы проверить, есть ли у вас такие проблемы, просто запретите использование оператора удаления вектора.

1 голос
/ 03 августа 2010

Спасибо за все ответы и комментарии.Все они были полезны и актуальны.

Любая дополнительная информация все еще приветствуется.


Ниже был комментарий на мой вопрос от Ганса Пассанта:

Как только вы начинаете экспортировать классы из DLL, компиляция с / MD становится очень важной.Для меня это выглядит как / MT.

В результате этого я более внимательно посмотрел на настройку связывания в проекте.Я нашел «скрытый» экземпляр / MT и / MTd, который должен был быть / MD и / MDd, а также некоторые связанные несоответствия в других настройках.

После исправления этих ошибок утверждение не выдается, и кодпохоже, ведет себя правильно.


Вот некоторые вещи, которые нужно проверить при возникновении сбоев или сбоев утверждений при выполнении, после чего вызываются области и деструкторы.Убедитесь, что во всех проектах (включая зависимости) и во всех конфигурациях (особенно в проблемных):

(Здесь пути * .vcproj относятся к.)

  • В C / C ++ выбрана правильная среда выполнения |Генерация кода |Библиотека времени исполнения;
  • Соответствующие определения (если таковые имеются) сделаны в C / C ++ |Препроцессор |Определения препроцессораособенно это касается использования статических динамических библиотек и (например, _STLP_USE_STATIC_LIB и _STLP_USE_DYNAMIC_LIB для STLport);
  • Соответствующие версии библиотек выбраны в Linker |Вход |Дополнительные зависимостиособенно это касается статических библиотек времени выполнения против «оболочек» для DLL (например, stlport_static.lib против stlport. NM .lib).

Интересно scalar «аромат» удаления, который, как я ожидал, все еще не вызывается (точка останова никогда не достигается).То есть я все еще вижу только деструктор удаления vector .Следовательно, это могла быть «красная сельдь».

Возможно, это просто проблема реализации Microsoft, или, может быть, есть еще какая-то тонкость, которую я пропустил.

1 голос
/ 30 июля 2010

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().

0 голосов
/ 06 февраля 2016

В моем случае, это неправильный «вкус» удаления деструктора кажется, что применяется: то есть вектор, а не скаляр.

Это не проблема здесь. В соответствии с псевдокодом в Несоответствие скаляра и вектора new и delete , scalar deleting destructor просто обращается к vector deleting descructor с флагом, говорящим «Делай скалярное разрушение, а не векторное уничтожение».

Ваша настоящая проблема, как отмечали другие авторы, заключается в том, что вы выделяете одну кучу, а удаляете другую. Самым ясным решением является дать вашим классам перегрузки operator new и operator delete, как я описал в ответе на аналогичный вопрос: Ошибка удаления std :: vector в DLL с использованием языка PIMPL

...