это == пустой указатель//, Как это может быть возможно?

Недавно я столкнулся с некоторым странным поведением своего приложения. Это было разработано главным образом в C#, но CLI/C++ также использовался для достижения лучшей производительности. Я получал Систему. NullReferenceException в очень простом методе в сравнении TimeSpan:

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

Было очевидно, что единственная ссылка, используемая в этом выражении, была неявна это (это. _ метка времени). Я добавил оператор контроля, и оказалось, что это является на самом деле пустым. После короткого расследования я справился к подготовленной короткой программе, представляющей это явление. Это - 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;
}

Кто-либо знает, как это может быть возможно? Действительно ли это - ошибка в компиляторе?

22
задан StanislawSwierc 20 April 2010 в 22:18
поделиться

2 ответа

В C ++ (и, предположительно, в C ++ / CLI) ничто не мешает вам попытаться вызвать методы с указателем NULL.В большинстве реализаций вызов виртуального метода завершится сбоем в точке вызова, потому что среда выполнения не сможет прочитать таблицу виртуальных методов. Однако вызов невиртуального метода - это просто вызов функции с некоторыми параметрами, одним из которых является указатель this . Если он равен нулю, то это то, что передается функции.

Я считаю, что результатом вызова любой функции-члена для указателя NULL (или nullptr ) официально является «неопределенное поведение».

19
ответ дан 29 November 2019 в 05:16
поделиться

Спасибо, Грег, за ваш ответ, все происходит именно так, как вы его описываете. Однако меня не устраивает эта ситуация, потому что это означает, что я должен помещать

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

в начало каждого метода. Только это гарантирует, что мой метод не появится наверху трассировки стека как ошибочный фрагмент кода без проверки аргументов.

Я никогда не встречал (this == null), когда писал на C #. Поэтому я решил узнать, чем он отличается от 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();
        }
    };
}

И небольшую программу на C #, которая использует классы C ++ / CLI с тем же методом Main:

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

Затем я разобрал их с помощью Red Gate .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 
}

важными частями являются:

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

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

Где:

  • call - вызывает метод, указанный переданным дескриптором метода.
  • callvirt - вызывает метод с поздним связыванием для объекта, помещая возвращаемое значение в стек оценки.

И теперь окончательный вывод:

Компилятор C # в VS 2008 рассматривает каждый метод как виртуальный, поэтому всегда можно предположить, что (this! = Null). В C ++ / CLI каждый метод вызывается должным образом, поэтому необходимо обращать внимание на вызовы не виртуальных методов.

12
ответ дан 29 November 2019 в 05:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: