c++ - memoria - punteros y arreglos en c
¿Por qué no se bloquea el programa cuando invoco una función miembro a través de un puntero nulo en C++? (6)
#include "iostream"
using namespace std;
class A
{
public:
void mprint()
{
cout<<"/n TESTING NULL POINTER";
}
};
int main()
{
A *a = NULL;
a->mprint();
return 0;
}
Estoy obteniendo salida como "TESTING NULL POINTER". ¿Alguien puede explicar por qué este programa está imprimiendo la salida en lugar de estrellarse? Lo comprobé en Dev C ++ y el compilador aCC dio el mismo resultado.
Esta es una llamada completamente legal.
entendamos como funciona
cuando se crea un nuevo objeto se crean sus variables miembro.
¿Qué pasa con las funciones de los miembros? Las funciones miembro no son noticias asignadas, siempre hay una copia de todas las funciones miembros. Por defecto, una variable miembro se agrega a cada función miembro que es este puntero que apunta al objeto en sí.
Cuando no hay ningún objeto presente, el puntero del objeto es un valor nulo. No importa porque no tienes acceso a ella de ninguna manera. Obtendrá problemas si utiliza este puntero de cualquiera de las variables miembro en el método. Esto se debe a que la variable miembro no es válida en caso de un puntero nulo.
en MFC tenemos el método GetSafeHwnd () para CWnd. Esto funciona en el mismo principio.
Los resultados de llamar a una función miembro utilizando un puntero nulo a un objeto no están definidos como comportamiento en c ++, por lo que puede hacer cualquier cosa.
En este caso es probable porque ha reescrito su función como si fuera así.
void mprint(A* this);
y tu llamada como esta
mprint(0);
Así que simplemente se llama como si fuera una función ordinaria y se pasa el puntero nulo como un parámetro que nunca se usa de ninguna manera. Eso explica por qué no falla, pero el compilador es libre de hacer casi cualquier cosa
No está utilizando ninguna variable miembro de A
: la función es completamente independiente de la instancia de A
y, por lo tanto, el código generado no contiene nada que no haga referencia a las diferencias 0. Esto sigue siendo un comportamiento indefinido : puede que funcione en algunos compiladores . Un comportamiento indefinido significa "cualquier cosa puede suceder", incluido el hecho de que el programa funciona como lo esperaba el programador.
Si, por ejemplo, mprint
virtual, puede sufrir un fallo, o puede que no obtenga uno si el compilador ve que en realidad no necesita un vtable.
Si agrega una variable miembro a A e imprime esto, obtendrá un bloqueo.
Respuesta simple: porque mprint()
no está usando ninguna de las variables miembro de la clase
Respuesta detallada: cuando se llama a un método de una clase, la instancia de la clase se pasa a la función de llamada (normalmente como primer argumento, sin embargo, en algunas convenciones de llamada como __thiscall, esto se pasa en un registro). Esta instancia de clase se usa para acceder a todas las variables miembro que se usan en el método de llamada.
En este caso, esta instancia es NULL pero esto no hace ninguna diferencia ya que no se están utilizando variables miembro en el método de llamada. Intente cambiar su código de manera que imprima el valor de una variable miembro en el método mprint()
y obtenga el bloqueo.
Según la especificación de C ++, este programa tiene un comportamiento indefinido porque está invocando una función miembro en un receptor nulo.
Sin embargo, la razón por la que esto funciona es que las funciones miembro no virtuales se implementan normalmente como funciones regulares que toman el puntero "this" como un primer argumento implícito. Por consiguiente, si llama a una función miembro en un puntero nulo, siempre que no use este puntero, su programa no se bloqueará. Por supuesto, no puedes confiar en esto; un compilador de C ++ válido podría hacer que esto se bloquee.
Sin embargo, las funciones virtuales son una historia diferente porque la función que realmente se llama debe resolverse en tiempo de ejecución. Esto generalmente implica una introspección en la tabla de funciones virtuales del receptor. Por lo tanto, si intenta llamar a una función miembro virtual en un puntero nulo, incluso si la función no tiene acceso a esto, todavía causará un bloqueo. ¡Prueba esto si tienes curiosidad!
Ser capaz de invocar funciones miembro no virtuales en punteros no válidos incluso permite codificar la información asociada a un objeto en el puntero en sí. Por ejemplo:
#include <iostream>
class MagicInteger {
public:
static MagicInteger* fromInt (int x) {
return reinterpret_cast<MagicInteger*>(x);
}
int getValue() {
return static_cast<int>(reinterpret_cast<intptr_t>(this));
}
private:
// forbid messing around
MagicInteger ();
MagicInteger (MagicInteger&);
MagicInteger& operator=(const MagicInteger&);
};
int main (void) {
MagicInteger* i = MagicInteger::fromInt(6);
std::cout << "Value is " << i->getValue() << std::endl;
return 0;
}
Esto también se puede usar para implementar punteros etiquetados , es decir, punteros que contienen metainformación sobre el pointee.
Estos dos modismos se utilizan en el javascript VM V8 de Google Chrome para representar enteros de 31 bits