Pour obtenir une clé
d'essai remplissez le formulaire ci-dessous
Demandez des tariffs
Nouvelle licence
Renouvellement de licence
--Sélectionnez la devise--
USD
EUR
RUB
* En cliquant sur ce bouton, vous acceptez notre politique de confidentialité

Free PVS-Studio license for Microsoft MVP specialists
To get the licence for your open-source project, please fill out this form
** En cliquant sur ce bouton, vous acceptez notre politique de confidentialité.

I am interested to try it on the platforms:
** En cliquant sur ce bouton, vous acceptez notre politique de confidentialité.

Message submitted.

Your message has been sent. We will email you at


If you haven't received our response, please do the following:
check your Spam/Junk folder and click the "Not Spam" button for our message.
This way, you won't miss messages from our team in the future.

>
>
>
Still Comparing "this" Pointe…

Still Comparing "this" Pointer to Null?

12 Déc 2013

A long time ago, in a galaxy far, far away there was a widely used MFC library which had a few classes with methods that compared "this" pointer to null.

This is a translation of an article written by Dmitry Meshcheryakov, an ABBYY employee and first published here: "ABBYY blog. Still Comparing "this" Pointer to Null?". Translation done and published with permission of the copyright holder.

0226_This/image1.png

It looked something like this:

class CWindow {
    HWND handle;
    HWND GetSafeHandle() const
    {
         return this == 0 ? 0 : handle;
    }
};

"It doesn't make any sense!", the readers will argue. Why, it "does": this code "allows" you to call the GetSafeHandle() method through a null CWindow* pointer. This method is sometimes used in different projects. Let's find out why doing so is really a bad idea.

First of all according to the C++ standard (it follows from the paragraph 5.2.5/3 of the standard ISO/IEC 14882:2003(E)), calling any nonstatic method of any class through a null-pointer leads to undefined behavior. However, a code shown below may work in certain implementations:

class Class {
public:
    void DontAccessMembers()
    {
        ::Sleep(0);
    }
};

int main()
{
    Class* object = 0;
    object->DontAccessMembers();
}

It can work because no attempts are made to access the class members while the method is executed, and no late binding is used to call the method. The compiler knows which particular method of which particular class should be called and simply adds the necessary call. "This" pointer is passed as a parameter. The effect produced is the same as if it were a static method:

class Class {
public:
    static void DontAccessMembers(Class* currentObject)
    {
        ::Sleep(0);
    }
};

int main()
{
    Class* object = 0;
    Class::DontAccessMembers(object);
}

If the method were called virtually, it would have required late binding which is usually implemented through a pointer to the virtual methods table in the beginning of a particular object. In this case even finding out which method to call would require accessing the object's content, and this operation would most likely cause a crash in case of a null-pointer.

But we know for sure that our method will never be called virtually, don't we? After all, this code has been working well for some years.

The trouble is that the compiler may utilize undefined behavior for the purpose of optimization. For example:

int divideBy = ...;
whatever = 3 / divideBy;
if( divideBy == 0 ) {
    // THIS IS IMPOSSIBLE
}

In this code fragment we have an integer division by divideBy. Integer division by null causes undefined behavior (usually a crash). Therefore we can assume that the divideBy variable is not equal to null and eliminate the check during compilation and optimize the code accordingly.

In the same way the compiler can optimize the code comparing "this" pointer to null. According to the Standard, this cannot be null and therefore the checks and the corresponding code branches can be eliminated, which will greatly affect the code dependent on the comparison of "this" pointer to null. The compiler has a full right to "break" (actually just break it further) the code CWindow::GetSafeHandle() and generate machine code which doesn't contain the comparison and only reads the class field all the time.

Currently even the freshest versions of the most popular compilers (you can check it with at the GCC Explorer service) don't perform such optimizations, so "everything works" for now, right?

First, you will be very disappointed to waste quite a lot of time to find out that there is such an optimization now, after having moved to another compiler or a new version of your current compiler. That's why the code shown above is non-portable.

Second,

class FirstBase {
    int firstBaseData;
};

class SecondBase {
public:
    void Method()
    {
        if( this == 0 ) {
            printf( "this == 0");
        } else {
            printf( "this != 0 (value: %p)", this );
        }
    }
};

class Composed1 : public FirstBase, public SecondBase {
};

int main()
{
    Composed1* object = 0;
    object->Method();
}

GOOD LORD, the "this" pointer equals 0x00000004 on entering the method when compiled in Visual C++ 9, as the pointer initially set to null is adjusted so that it points to the beginning of a subobject of the corresponding class.

If you change the sequence order of the base classes:

class Composed2 : public SecondBase, public FirstBase {
};
    
int main()
{
    Composed2* object = 0;
    object->Method();
}

this will become null at the same conditions because the beginning of the subobject coincides with the beginning of the object it is included into. Thus we get a wonderful class whose method works only if this class is used "appropriately" in compound objects. I wish you good luck with debugging; the Darwin award has seldom been so close.

One can easily notice that implicit conversion of the pointer to the object to a pointer to the subobjectworks "wrong" in the case of the Composed1 class: this conversion yields a non-null pointer to the subobject from a null-pointer. When implementing a conversion of the same meaning, the compiler usually adds a check of the pointer for being null. For example, compilation of the following code with undefined behavior (the Composed1 class is the same as shown above):

SecondBase* object = reinterpret_cast<Composed1*>( rand() );
object->Method();

produces the following machine code in Visual C++ 9:

SecondBase* object = reinterpret_cast<Composed1*>( rand() );
010C1000  call        dword ptr [__imp__rand (10C209Ch)] 
010C1006  test        eax,eax
010C1008  je          wmain+0Fh (10C100Fh) 
010C100A  add         eax,4 
object->Method();
010C100D  jne         wmain+20h (10C1020h) 
010C100F  push        offset string "this == 0" (10C20F4h) 
010C1014  call        dword ptr [__imp__printf (10C20A4h)] 
010C101A  add         esp,4

The second instruction in this machine code is a comparison of the pointer to the object to null. If the check reveals that the pointer is equal to null, the control is not allowed to pass through the add eax,4 instruction which is used to shift the pointer. An implicit conversion here is implemented with a check, though it was also possible to call the method through the pointer and assume that the pointer is non-null.

In the first case (calling the subobject's class method straight through the pointer to the class object), the fact of the pointer being equal to null also corresponds to undefined behavior, and the check is not added here. If you thought it to be rubbish and fantasy when reading the paragraph about optimization of a code with a method call and pointer check for null after it, you shouldn't: the case described above is just the one where such an optimization has actually been used.

It's a bad idea to rely on calling a nonstatic method through a null-pointer. If you want to enable a method to be executed for a null pointer, you should make the method static and explicitly pass the pointer to the object as a parameter.

Popular related articles
"Our legacy of the past" or why we divided the V512

Date: 12 Aoû 2022

Author: Mikhail Gelvih

As the saying goes, the first step is always the hardest. That's exactly what happened in our case – after delaying it for so long, we have finally split the V512 diagnostic rule. You can read more a…
Why do arrays have to be deleted via delete[] in C++

Date: 27 Jul 2022

Author: Mikhail Gelvih

This note is for C++ beginner programmers who are wondering why everyone keeps telling them to use delete[] for arrays. But, instead of a clear explanation, senior developers just keep hiding behind …
Intermodular analysis of C and C++ projects in detail. Part 2

Date: 14 Jul 2022

Author: Oleg Lisiy

In part 1 we discussed the basics of C and C++ projects compiling. We also talked over linking and optimizations. In part 2 we are going to delve deeper into intermodular analysis and discuss its ano…
Intermodular analysis of C and C++ projects in detail. Part 1

Date: 08 Jul 2022

Author: Oleg Lisiy

Starting from PVS-Studio 7.14, the C and C++ analyzer has been supporting intermodular analysis. In this two-part article, we'll describe how similar mechanisms are arranged in compilers and reveal s…
Four reasons to check what the malloc function returned

Date: 20 Avr 2022

Author: Andrey Karpov

Some developers may be dismissive of checks: they deliberately do not check whether the malloc function allocated memory or not. Their reasoning is simple — they think that there will be enough memor…

Comments (0)

Next comments
Unicorn with delicious cookie
Nous utilisons des cookies pour améliorer votre expérience de navigation. En savoir plus
Accepter