c++ - ¿Por qué se llama al vector destructor destructor como resultado de una eliminación escalar?
debugging visual-c++ (5)
Tengo un código que está fallando en un sistema grande. Sin embargo, el código esencialmente se reduce al siguiente pseudo-código. He eliminado gran parte de los detalles, ya que he tratado de reducirlo a cero; No creo que esto pase por alto algo crucial sin embargo.
// 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...
};
y:
// in the EXE:
MyClassImp* myObj = new MyClassImp ( some_arguments ); // scalar new
// ... and literally next (as part of my cutting-down)...
delete myObj; // scalar delete
Tenga en cuenta que se está usando la eliminación escalar nueva y escalar coincidente.
En una compilación de depuración en Visual Studio (2008 Pro), en <dbgheap.c> de Microsoft, la siguiente aserción falla:
_ASSERTE(_CrtIsValidHeapPointer(pUserData));
Cerca de la parte superior de la pila se encuentran los siguientes elementos:
mydll_d.dll!operator delete()
mydll_d.dll!MyClassImp::`vector deleting destructor''()
Creo que esto debería ser
mydll_d.dll!MyClassImp::`scalar deleting destructor''()
Es decir, el programa se comporta como si hubiera escrito
MyClassImp* myObj = new MyClassImp ( some_arguments );
delete[] newObj; // array delete
La dirección en pUserData
es la de myObj
(a diferencia de un miembro). La memoria alrededor de esa dirección se ve así:
... 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
...
donde los cuatro VV
son presumiblemente la dirección de la tabla de funciones virtuales, el MM...MM
es un dato miembro reconocible, y los otros bytes son varios marcadores especiales establecidos por el depurador (por ejemplo, los FD FD
son ''bytes de guardia''). ''alrededor del almacenamiento del objeto).
Poco antes de la falla de aserción, veo que el VV
cambia, y me pregunto si eso se debe a un cambio a la tabla de funciones virtuales de la clase base.
Soy consciente del problema del nivel incorrecto en la jerarquía de la clase que sufre destrucción. Ese no es el problema aquí; mis destructores son todos virtuales
Noto la página de Microsoft "ERROR: Operador incorrecto, elimine la llamada para la clase exportada" http://support.microsoft.com/kb/122675, pero parece que se trata del ejecutable incorrecto (con el montón incorrecto) que intenta asumir la responsabilidad de la destrucción de los datos.
En mi caso, es que parece que se está aplicando el "sabor" incorrecto de eliminar el destructor: es decir, vector en lugar de escalar.
Estoy en el proceso de tratar de producir un código de reducción mínimo que todavía muestre el problema.
Sin embargo, cualquier sugerencia o sugerencia para ayudar con la forma de investigar más este problema sería muy apreciada.
Quizás la pista más importante aquí es el mydll_d.dll!operator delete()
en la pila. ¿Debo esperar que sea myexe_d.exe!operator delete()
, lo que indica que los DLLEXP
se han "perdido"?
Supongo que esto podría ser una instancia de eliminación doble (pero no lo creo).
¿Hay alguna buena referencia que pueda leer sobre qué _CrtIsValidHeapPointer
busca?
Microsoft proporciona la fuente de su tiempo de ejecución de C; puede verificar allí para ver qué _CrtIsValidHeapPointer
hace. En mi instalación, está en C:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/crt/src/dbgheap.c
.
Otra sugerencia es verificar el desmontaje de
delete newObj; // scalar delete
y compararlo con el desmontaje generado para
delete[] newObj;
y
delete pointerToClassLikeMyClassThatIsInExeAndNotDll;
para probar tu teoría sobre la delete[]
de delete[]
. Del mismo modo, puede consultar la pila de llamadas para
delete pointerToClassLikeMyClassThatIsInExeAndNotDll;
para probar su teoría sobre mydll_d.dll!operator delete()
contra myexe_d.exe!operator delete()
.
Parece que esto podría ser una cuestión de asignar un montón e intentar eliminarlo en otro. Esto puede ser un problema al asignar objetos desde un dll ya que el dll tiene su propio montón. Según el código que estás mostrando, no parece que este sea el problema, pero ¿tal vez en la simplificación se perdió algo? En el pasado, he visto códigos como este que usan funciones de fábrica y métodos de destroy
virtual en los objetos para asegurarse de que la asignación y eliminación ocurra en el código dll.
Gracias por todas las respuestas y comentarios. Todos han sido útiles y relevantes.
Cualquier información adicional es bienvenida.
Lo siguiente fue un comentario sobre mi pregunta de Hans Passant:
Una vez que empiezas a exportar clases desde DLL, la compilación con / MD se vuelve muy importante. Parece / MT para mí.
Como resultado de eso, eché un vistazo más de cerca a la configuración de vinculación durante todo el proyecto. Encontré una instancia ''enterrada'' de / MT y / MTd que debería haber sido / MD y / MDd, además de algunas incoherencias relacionadas en otras configuraciones.
Una vez corregidos, no se lanza ninguna afirmación, y el código parece estar comportándose correctamente.
A continuación, se incluyen algunas de las cosas que se deben comprobar cuando se producen bloqueos o fallas de afirmación en la ejecución. Se llaman alcances y destructores. Asegúrese de que en todos los proyectos (incluidas las dependencias) y en todas las configuraciones (especialmente en la problemática):
(Aquí las rutas * .vcproj son relativas a </ VisualStudioProject / Configurations / Configuration />.)
- El tiempo de ejecución correcto se selecciona en C / C ++ | Generación de código | Runtime Library <Herramienta [@ Name = "VCCLCompilerTool"] / @ RuntimeLibrary>;
- Las definiciones apropiadas (si las hay) se hacen en C / C ++ | Preprocesador | Definiciones del preprocesador <Herramienta [@ Name = "VCCLCompilerTool"] / @ PreprocessorDefinitions> especialmente en relación con el uso de bibliotecas estáticas frente a dinámicas (por ejemplo, _STLP_USE_STATIC_LIB versus _STLP_USE_DYNAMIC_LIB para STLport);
- Las versiones apropiadas de las bibliotecas se seleccionan en Linker | Entrada | Dependencias adicionales <Herramienta [@ Name = "VCLinkerTool"] / @ AdditionalDependencies> especialmente en relación con las bibliotecas de tiempo de ejecución estáticas frente a las ''envolturas'' para las DLL (por ejemplo, stlport_static.lib frente a stlport. NM .lib).
Curiosamente, el "sabor" escalar de la eliminación que esperaba todavía no parece ser invocado (nunca se golpea el punto de interrupción). Es decir, solo veo el vector eliminando el destructor. Por lo tanto, eso puede haber sido una "pista falsa".
Tal vez esto es solo un problema de implementación de Microsoft, o tal vez todavía hay alguna otra sutileza que me he perdido.
Este comportamiento es especial para MSVC 9, donde el operador de eliminación para una clase exportada, que tiene un destructor virtual se genera implícitamente y se destroza a vector dtor con un indicador asociado, donde 1 significa (escalar) y 3 significa (vector).
El verdadero problema con esto es que rompe la forma canónica de nuevo / eliminar, donde el codificador del cliente no puede desactivar el operador de eliminación de vectores en su código, si lo cree, es una mala idea usarlo.
Además, el vector dtor, también parece ejecutarse incorrectamente, si el nuevo se asigna en otro módulo que el módulo en el que reside la implementación y luego se mantiene en una variable estática a través de un recuento de referencias, que ejecuta un eliminar esto (aquí el vector dtor entra en juego) en el cierre del proceso.
Esto coincide con el problema de montón ''bshields'' ya mencionado anteriormente, el dtor se ejecuta en el montón incorrecto y el código crahses con ''no se puede leer esa ubicación de memoria'' o ''violación de acceso'' al apagar - tales problemas parecen ser muy comunes.
La única forma de evitar este error es prohibir el uso de un destructor virtual y ejecutarlo usted mismo, imponiendo el uso de una función delete_this de la clase base, estúpido ya que imita lo que el dtor virtual debería hacer por usted. . Luego también se ejecuta el escalador dtor y al apagar cualquier objeto refominado compartido entre los módulos, se puede crear una instancia de forma segura, ya que el apilamiento siempre se dirige correctamente al módulo de origen.
Para verificar, si tiene tales problemas, simplemente prohíba el uso del operador vector delete.
En mi caso, es que parece que se está aplicando el "sabor" incorrecto de eliminar el destructor: es decir, vector en lugar de escalar.
Ese no es el problema aquí. Según el pseudocódigo en Desparejo escalar y vector nuevo y eliminar , el scalar deleting destructor
simplemente llama al vector deleting descructor
con una bandera que dice "Hacer destrucción escalar en lugar de destrucción del vector".
Su problema real, como lo señalan otros carteles, es que está asignando en un montón y eliminando en otro. La solución más clara es dar a sus clases una sobrecarga de operator new
y operator delete
, como describí en una respuesta a una pregunta similar: Error al eliminar std :: vector en un archivo DLL utilizando el modismo PIMPL