Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

this == null // How can it be possible?

Tags:

Recently I came across some strange behaviour of my application. It has been developed mainly in C# but CLI/C++ was also used to achieve better performance. I was getting a System.NullReferenceException in a very simple method at the TimeSpan comparison:

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

It was obvious that the only reference used in this expression was implicit this (this._timestamp). I added an assert statement and it turned out that this is actually null. After short investigation I managed to prepared short program presenting this phenomenon. It is 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; } 

Does anybody know how can it be possible? Is it a bug in the compiler?

like image 596
StanislawSwierc Avatar asked Apr 20 '10 22:04

StanislawSwierc


People also ask

Can this ever be null?

tl;dr, "this" can only be called from a non-static method and we all know that a non-static method is called from some sort of object which cannot be null.

How do you check if this is null?

To check for null variables, you can use a strict equality operator ( === ) to compare the variable with null . This is demonstrated below, where the boolean expression evaluates to true for only for null and evaluates to false for other falsy values.

Can null == null?

If you think of it from a programming (i.e. pointer reference) point of view then, yes, two references of null have the same pointer value and, since most of the popular languages will fall back to pointer-equality if no custom equality is defined, null does equal null.

Can this keyword be assigned null value?

null can only be assigned to reference type, you cannot assign null to primitive variables e.g. int, double, float, or boolean. The compiler will complain if you do so, as shown below.


2 Answers

In C++ (and presumably in C++/CLI) there's nothing preventing you from trying to call methods on a NULL pointer. In most implementations, a virtual method call will crash at the point of the call because the runtime won't be able to read the virtual method table. However, a non-virtual method call is just a function call with some parameters, one of which is the this pointer. If it's null, then that is what is passed to the function.

I believe the result of calling any member function on a NULL (or nullptr) pointer is officially "undefined behaviour".

like image 50
Greg Hewgill Avatar answered Sep 21 '22 08:09

Greg Hewgill


Thank you Greg for your answer it happens the way you describe it. However, I am not pleased with this situation because it means that I have to place

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

at the beginning of every method. Only this would guarantee that my method won't appear at the top of stack-trace as a faulty piece of code without argument validation.

I have never come across (this == null) when I was writing in C#. Therefore, I decided to find out how is it different from C++/CLI. I created a sample application in 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();         }     }; } 

And a small program in C# that uses the C++/CLI classes with the same Main method:

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

Then I disassembled them with Red Gate's .NET Reflector:

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  } 

The important parts are:

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

Where:

  • call - Calls the method indicated by the passed method descriptor.
  • callvirt - Calls a late-bound method on an object, pushing the return value onto the evaluation stack.

And now the final conclusion:

C# compiler in VS 2008 treats every method as if it was virtual, thus it it always safe to assume that (this != null). In C++/CLI every method is called as it should so it is necessary to pay attention to the non-virtual method calls.

like image 33
StanislawSwierc Avatar answered Sep 20 '22 08:09

StanislawSwierc