2

The problem is that an implicit cast of nullptr, to the second super class, of a class with multiple inheritance results (at least with LLVM 7.0.2) in an adjustment being applied to the nullptr. The pointer is now no longer null which (if null checks are being performed in the methods of the super class) can result in a crash (I guess technically undefined behaviour).

Here's a minimal example:

#include <iostream>

inline bool pointerIsNotNull(const void* ptr) { return ptr != nullptr; }

class IntValue {
    public:
        IntValue() { }
        int getIntValue() { return pointerIsNotNull(this) ? value : 0; }
    private:
        int value;
};

static const char* nullptrChar = "nullptr";

class CharValue {
    public:
        CharValue() { }
        const char* getCharValue() { return pointerIsNotNull(this) ? value : nullptrChar; }
    private:
        char* value;
};

class Foo : public IntValue, public CharValue {
    public:
        Foo() { }
        double getDoubleValue() { return pointerIsNotNull(this) ? value : 0; }
    protected:
        double value;
};

int main(int argc, const char * argv[])
{
    Foo* foo = nullptr;

    std::cout << foo->getIntValue() << std::endl;

    CharValue* charValue = foo;
    std::cout << charValue->getCharValue() << std::endl;

    std::cout << foo->getCharValue() << std::endl;
}

My question is this: Is there a way to check for this kind of shenanigans without manually checking for nullptr before calls into a second superclass?

You know, is there an elegant way to do this (maybe in the second superclass) which would assure me that I've caught all possible examples of this behaviour?

EDIT: Yes, I know calling member functions from a nullptr isn't modern practice. I thought (until I posted this question) that it used to be accepted practice and in any case I'm constrained by standards I don't have control over. So, with the assumption that calling a member function on a nullptr will always enter the correct function, is there an elegant solution to my problem?

Ben Whale
  • 493
  • 2
  • 9
  • 2
    What you're doing results in undefined behavior - do you really need to know how to do this, or is this purely a question of the form "the undefined behavior that results from performing this operation on this one compiler is X, and I'd like to avoid it?" – templatetypedef Jan 27 '16 at 22:30
  • 1
    `pointerIsNull` being false when the pointer *is* null? – molbdnilo Jan 27 '16 at 22:43
  • This would be a perfect counterexample for all the people who think it's OK to call member functions on a null pointer so long as the function doesn't write local variables (or some other arbitrary list of conditions) – M.M Jan 27 '16 at 23:01
  • @molbdnilo Ah. Good spotting. I've edited the question. – Ben Whale Jan 28 '16 at 01:27
  • @M.M Well you may have well hit the nail on the head. For those at home here's a discussion of calling member functions on nullptr that refers to the standard: http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-beha – Ben Whale Jan 28 '16 at 01:30
  • @M.M I'm constrained by the organisation practises where I work. The technique used to handle nullptr's is essentially what I've written above, so what can be done within the constraints I have? I've edited the question to make this clearer. – Ben Whale Jan 28 '16 at 01:31
  • 1
    @BenWhale Now you introduced more bugs. – molbdnilo Jan 28 '16 at 01:32
  • 1
    write the useless code that they want, and collect your pay check... – M.M Jan 28 '16 at 01:34
  • 3
    "It used to be fine" - it was never fine. If you want your code to actually work, you need to do the check before calling the function. You *could* write something inside the function like `if ((uintptr_t)this < 1000)` , but this check could be bypassed at any time by the optimizer. Hopefully you don't work in any field where it'd be a serious issue if the code didn't work as intended – M.M Jan 28 '16 at 01:38
  • Ha hah ah! Hopefully I got the pointerIsNull() thing sorted! Editing too quickly. – Ben Whale Jan 28 '16 at 01:40
  • @M.M Are you sure "never used to be fine"? I thought that before the c++98 there were expectations that this could be null? Moreover I thought the enforcing the undefined behaviour thing only became serious after c++11? – Ben Whale Jan 28 '16 at 01:51
  • 2
    C++11 didn't change anything in this respect. I guess you could say that before there was any standard at all, there was no such thing as defined or undefined behaviour. The history of C and C++ are full of code where people tried something and it appeared to work, then it broke at some point in the future. The idea of the standard is to help people write code that will always work. I'm sure if you went back to 1994 and asked the same question you'd get a lot of people saying "don't do that". I suspect that you would have hit the same MI adjustment problem even on 1994 compilers – M.M Jan 28 '16 at 02:00
  • 1
    @M.M Wow, you make it sound like 1994 was a long time ago ;-) (I think I wrote my first lines of C++ in '94.) Programmers: making the same mistakes over and over for decades, because rapid progress demands it. – molbdnilo Jan 28 '16 at 02:57

1 Answers1

6

foo->getIntValue() is undefined when foo is the null pointer, which makes your entire program undefined.
That is, the dereferencing itself is undefined, and your program is doomed before it reaches the check.

There is no point in checking whether this is null, since a compiler is free to assume that it isn't (if it were, the program would be undefined, so the compiler can do whatever it wants).

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
  • In other situations this answer would be fine, but I'm dealing with code that it literally riddled with this, for want of a better word, anti-pattern. It's certainly not modern practice, but it used to be. So with the constraint that I must live with this way dealing with nullptr's and with the assumption that calling a member function on a nullptr of the appropriate type will always enter the member function... is there a good way to deal with this issue? – Ben Whale Jan 28 '16 at 01:36
  • 2
    It never used to be practice. People have been *getting away with it* largely because compilers used to be much worse than they are. – molbdnilo Jan 28 '16 at 01:54