2010-01-15 9 views
6

Actualmente estoy depurando un registro de fallas. El bloqueo se produce porque el puntero vtable de un objeto (C++ -) es 0x1, mientras que el resto del objeto parece estar bien por lo que puedo decir del registro de fallas.¿Bajo qué circunstancias puede un puntero vtable ser nulo (o 0x1)?

El programa se bloquea cuando intenta llamar a un método virtual.

Mi pregunta: ¿Bajo qué circunstancias puede un puntero vtable convertirse en nulo? ¿El operador delete establece el puntero vtable en nulo?

Esto ocurre en OS X usando gcc 4.0.1 (Apple Inc. build 5493).

+0

Puede aclarar si el puntero de función en la viable es 0 o si es la de que este puntero es nulo? ¿O es un puntero en el objeto que apunta a la vtable actual? –

+0

El puntero vtable es 1. El bloqueo se produce al intentar ejecutar 'call * 0x1c (% eax)' (en sintaxis at & t) y el valor de eax es 1 (no cero como dije incorrectamente). – Tobias

+0

Ejecútelo en gdb, establezca un punto de observación en la dirección, vea qué lo está escribiendo. –

Respuesta

7

Podría ser un pisoteo de memoria - algo que se escribe sobre eso vtable por error. Hay una cantidad casi infinita de formas de "lograr" esto en C++. Un desbordamiento de búfer, por ejemplo.

7

Cualquier tipo de comportamiento indefinido que puede conducir a esta situación. Por ejemplo:

  • Errores en la aritmética del puntero u otros que hacen que su programa escriba en memoria no válida.
  • Variables sin inicializar, moldes no válidos ...
  • El tratamiento de una matriz polimórficamente puede causar esto como un efecto secundario.
  • Intentando usar un objeto después de eliminarlo.

Consulte también las preguntas What’s the worst example of undefined behaviour actually possible? y What are all the common undefined behaviour that a C++ programmer should know about?.

Su mejor opción es usar un limite y un corrector de memoria, como una ayuda para la eliminación de errores.

1

Depende totalmente de la implementación. Sin embargo, sería bastante seguro asumir que después de eliminar alguna otra operación puede establecer el espacio de memoria como nulo.

Otras posibilidades incluyen sobreescritura de la memoria por algún puntero suelta - en realidad en mi caso es casi siempre presente ...

Dicho esto, nunca se debe tratar de utilizar un objeto después de borrar.

+1

borrar ciertamente puede hacerlo, pero no estoy al tanto de ninguna implementación donde lo haga –

+0

@Neil: las implementaciones no son mi campo. Cambié la respuesta a algo "más cierto" ... –

3

Mi primera suposición sería que algún código es memset() 'es un objeto de clase.

5

Un caso muy común: tratar de llamar a un método virtual pura desde el constructor ...

constructores

struct Interface 
{ 
    Interface(); 
    virtual void logInit() const = 0; 
}; 

struct Concrete: Interface() 
{ 
    virtual void logInit() const { std::cout << "Concrete" << std::endl; } 
}; 

Ahora, supongamos que la siguiente aplicación de Interface()

Interface::Interface() {} 

Entonces todo está bien:

Concrete myConcrete; 
myConcrete.pure(); // outputs "Concrete" 

Es tan doloroso llamar puro al constructor, sería mejor factorizar el código ¿verdad?

Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;) 

¡Entonces podemos hacerlo en una línea!

Concrete myConcrete; // CRASHES VIOLENTLY 

¿Por qué?

Porque el objeto está construido de abajo hacia arriba. Veámoslo.

instrucciones para construir una clase Concrete (más o menos)

  1. asignar suficiente memoria (por supuesto), y suficiente memoria para la _vtable también (1 función de puntero por función virtual, por lo general en el orden en que se declaran , partiendo de la base de más a la izquierda)

  2. llamada Concrete constructor (el código que no se ve)

    a> Call Interface constructor, que inicializar el _vtabl E con sus punteros

    b> Llamada cuerpo Interface del constructor (que escribió eso)

    c> Reemplaza los punteros en el _vtable en dichos métodos de anulación de hormigón

    d> Llamada cuerpo Concrete del constructor (que escribió que)

¿Cuál es el problema? Bueno, mira b> y c> orden;)

Cuando llamas a un método virtual desde un constructor, no hace lo que estás esperando. Va a la _vtable para buscar el puntero, pero el _vtable aún no se ha inicializado por completo. Así, por todo lo que importa, el efecto de:

D() { this->call(); } 

es, de hecho:

D() { this->D::call(); } 

Al llamar a un método virtual desde dentro de un constructor, no lo hace el tipo dinámico del objeto al construirse, tiene el tipo estático del Constructor actual invocado.

En mi ejemplo Interface/Concrete, significa Interface tipo, y el método es virtual pura, por lo que el _vtable no se sostiene un puntero real (0x0 o 0x01 por ejemplo, si su compilador es lo suficientemente amable a los valores de depuración de configuración a Ayudamos allí).

destructores

Coincidentemente, vamos a examinar el caso Destructor;)

struct Interface { ~Interface(); virtual void logClose() const = 0; } 
Interface::~Interface() { this->logClose(); } 

struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; } 

Concrete::~Concrete() { delete[] m_data; } // It's all about being clean 
void Concrete::logClose() 
{ 
    std::cout << "Concrete refering to " << m_data << std::endl; 
} 

Así que lo que sucede en la destrucción? Bueno, el _vtable funciona bien, y se invoca el tipo de tiempo de ejecución real ... lo que significa aquí, sin embargo, es un comportamiento indefinido, porque ¿quién sabe qué pasó con m_data después de haber sido eliminado y antes de invocar Interface destructor?No lo hago;)

Conclusión

Nunca jamás llamar a los métodos virtuales desde el interior de constructores o destructores.

Si no es eso, uno se queda con una corrupción de memoria, mala suerte;)

+0

Muchas gracias por su respuesta detallada. En mi caso, este no es el caso, desafortunadamente. – Tobias

Cuestiones relacionadas