.net this==null//这怎么可能?

.net this==null//这怎么可能?,.net,visual-c++,c++-cli,.net,Visual C++,C++ Cli,最近我发现我的应用程序有一些奇怪的行为。它主要是在C#中开发的,但也使用CLI/C++来实现更好的性能。在TimeSpan比较中,我用一个非常简单的方法得到了System.NullReferenceException: TimeSpan _timestamp; void UpdateFrame(TimeSpan timestamp) { if(TimeSpan::Equals(_timestamp, timestamp) == false) 很明显,这个表达式中使用的唯一引用是隐式t

最近我发现我的应用程序有一些奇怪的行为。它主要是在C#中开发的,但也使用CLI/C++来实现更好的性能。在TimeSpan比较中,我用一个非常简单的方法得到了System.NullReferenceException:

TimeSpan _timestamp;
void UpdateFrame(TimeSpan timestamp)
{
    if(TimeSpan::Equals(_timestamp, timestamp) == false) 
很明显,这个表达式中使用的唯一引用是隐式this(this.\u timestamp)。我添加了一个assert语句,结果证明它实际上是空的。经过短暂的调查,我成功地准备了一个简短的节目来呈现这种现象。它是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;
}
使用名称空间系统;
使用名称空间系统::反射;
非托管的公共类
{
公众:
int值;
};
公共参考类管理
{
公众:
int值;
非托管*GetUnmanaged()
{
抽样方法();
返回新的非托管();
}
void SampleMethod()
{
系统::诊断::调试::断言(this!=nullptr);
该->值=0;
}
};
公共引用类ManagedAccessor
{
公众:
物业管理^ m;
};
int main(数组^args)
{
ManagedAccessor ^ma=gcnew ManagedAccessor();
//确认ma->m==null
系统::诊断::调试::断言(ma->m==nullptr);
//对空引用调用方法
删除ma->m->GetUnmanaged();
返回0;
}
有人知道这怎么可能吗?是编译器中的一个bug吗?在C++中(

)(可能是C++ + CLI),没有什么可以阻止你试图调用空指针上的方法。在大多数实现中,虚拟方法调用将在调用点崩溃,因为运行时无法读取虚拟方法表。但是,非虚方法调用只是带有一些参数的函数调用,其中一个参数是
this
指针。如果为null,则传递给函数的就是null


我相信在
NULL
(或
nullptr
)指针上调用任何成员函数的结果都是正式的“未定义行为”。

谢谢Greg的回答,它是按照您描述的方式发生的。然而,我对这种情况并不满意,因为这意味着我不得不放弃

if(this == nullptr) throw gcnew ArgumentException("this");
在每个方法的开头。只有这样才能保证我的方法在没有参数验证的情况下不会作为错误代码出现在堆栈跟踪的顶部

我在用C#写东西的时候从来没有遇到过(this==null)。因此,我决定找出它与C++/CLI的区别。我在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();
        }
    };
}
然后,我用Red Gate的.NET反射器将它们拆开:

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 
}
重要的部分是:

C++/CLI
L_0005: call instance void ThisEqualsNull.A::SampleMethod()

C#
L_0004: callvirt instance void [ThisEqualsNull]ThisEqualsNull.A::SampleMethod()
其中:

  • 调用-调用传递的方法描述符指示的方法
  • callvirt-调用对象上的后期绑定方法,将返回值推送到计算堆栈上
现在是最后的结论:


VS2008中的C#编译器将每个方法都视为虚拟方法,因此可以安全地假设(this!=null)。在C++/CLI中,每个方法都按其应该的方式调用,因此有必要注意非虚拟方法调用。未定义的行为使任何(或所有)事情都成为可能。
C++/CLI
L_0005: call instance void ThisEqualsNull.A::SampleMethod()

C#
L_0004: callvirt instance void [ThisEqualsNull]ThisEqualsNull.A::SampleMethod()