.net visual-c++ c++-cli

.net - this== null// ¿Cómo puede ser posible?



visual-c++ c++-cli (2)

En C ++ (y presumiblemente en C ++ / CLI) no hay nada que le impida tratar de llamar a los métodos en un puntero NULL. En la mayoría de las implementaciones, una llamada al método virtual se bloqueará en el punto de la llamada porque el tiempo de ejecución no podrá leer la tabla del método virtual. Sin embargo, una llamada a un método no virtual es solo una llamada a función con algunos parámetros, uno de los cuales es this puntero. Si es nulo, entonces eso es lo que se pasa a la función.

Creo que el resultado de llamar a cualquier función miembro en un puntero NULL (o nullptr ) es oficialmente un "comportamiento indefinido".

Recientemente me encontré con un comportamiento extraño de mi aplicación. Se ha desarrollado principalmente en C # pero también se usó CLI / C ++ para lograr un mejor rendimiento. Estaba obteniendo una System.NullReferenceException en un método muy simple en la comparación TimeSpan:

TimeSpan _timestamp; void UpdateFrame(TimeSpan timestamp) { if(TimeSpan::Equals(_timestamp, timestamp) == false)

Era obvio que la única referencia utilizada en esta expresión era implícita esto (this._timestamp). Agregué una declaración afirmativa y resultó que esto es realmente nulo. Después de una breve investigación, pude preparar un breve programa para presentar este fenómeno. Es C ++ / CLI.

using namespace System; using namespace System::Reflection; public class Unmanaged { public: int value; }; public ref class Managed { public: int value; Unmanaged* GetUnmanaged() { SampleMethod(); return new Unmanaged(); } void SampleMethod() { System::Diagnostics::Debug::Assert(this != nullptr); this->value = 0; } }; public ref class ManagedAccessor { public: property Managed^ m; }; int main(array<System::String ^> ^args) { ManagedAccessor^ ma = gcnew ManagedAccessor(); // Confirm that ma->m == null System::Diagnostics::Debug::Assert(ma->m == nullptr); // Invoke method on the null reference delete ma->m->GetUnmanaged(); return 0; }

¿Alguien sabe cómo puede ser posible? ¿Es un error en el compilador?


Gracias Greg por tu respuesta, sucede de la forma en que la describes. Sin embargo, no estoy satisfecho con esta situación porque significa que tengo que colocar

if(this == nullptr) throw gcnew ArgumentException("this");

al comienzo de cada método Solo esto garantizaría que mi método no aparecerá en la parte superior de la pila de rastreo como una pieza defectuosa de código sin validación de argumentos.

Nunca me he encontrado (esto == nulo) cuando escribía en C #. Por lo tanto, decidí averiguar cómo es diferente de C ++ / CLI. Creé una aplicación de muestra en C ++ / CLI:

namespace ThisEqualsNull{ public ref class A { public: void SampleMethod() { System::Diagnostics::Debug::Assert(this != nullptr); } }; public ref class Program{ public: static void Main(array<System::String ^> ^args) { A^ a = nullptr; a->SampleMethod(); } }; }

Y un pequeño programa en C # que usa las clases C ++ / CLI con el mismo método Principal:

class Program { static void Main(string[] args) { A a = null; a.SampleMethod(); } }

Luego los desensamblé con el Reflector .NET de Red Gate:

C++/CLI .method public hidebysig static void Main(string[] args) cil managed { .maxstack 1 .locals ( [0] class ThisEqualsNull.A a) L_0000: ldnull L_0001: stloc.0 L_0002: ldnull L_0003: stloc.0 L_0004: ldloc.0 L_0005: call instance void ThisEqualsNull.A::SampleMethod() L_000a: ret } C# .method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ( [0] class [ThisEqualsNull]ThisEqualsNull.A a) L_0000: nop L_0001: ldnull L_0002: stloc.0 L_0003: ldloc.0 L_0004: callvirt instance void [ThisEqualsNull]ThisEqualsNull.A::SampleMethod() L_0009: nop L_000a: ret }

Las partes importantes son:

C++/CLI L_0005: call instance void ThisEqualsNull.A::SampleMethod() C# L_0004: callvirt instance void [ThisEqualsNull]ThisEqualsNull.A::SampleMethod()

Dónde:

  • llamada - Llama al método indicado por el descriptor de método aprobado.
  • callvirt - Llama a un método de destino tardío en un objeto, empujando el valor de retorno en la pila de evaluación.

Y ahora la conclusión final:

El compilador de C # en VS 2008 trata cada método como si fuera virtual, por lo que siempre es seguro suponer que (esto! = Nulo). En C ++ / CLI, todos los métodos se llaman como deberían, por lo que es necesario prestar atención a las llamadas a métodos no virtuales.