1

I have a simple class:

class B
{
public:
    int getData() { return 3; }
};

then, I initialize a pointer to it with nullptr:

B *foo{ nullptr };

And then, trying to use it comes the surprise:

int t = foo->getData();

and t is now 3. How is that possible without constructing the class? Is it because getData() does not use "this"? That broke all my knowledge about pointers.

Is that expected behavior? I am working in Visual Studio 2013.

LeDYoM
  • 949
  • 1
  • 6
  • 21
  • 1
    It works because `foo->getData()` is equivalent to `getData(foo);` and the callee never *dereferences* the argument. Note that even though it works, it is categorized as *undefined behaviour* in C++. – Nawaz Mar 01 '16 at 08:34
  • Thanks all for the answers. In my opinion it should not be undefined behavior, it should crash. This is security issue from my point of view. If a method receives a pointer to a class and this pointer is nullptr, I can use this pointer to call a method. Yeah, I know is not guaranteed it will work, but is a hole in your library. – LeDYoM Mar 01 '16 at 08:48
  • LeDYoM: "*In my opinion it should not be undefined behavior, it should crash."* Please have patience with the language, read the design goal of C++ and a bit of its history. – Nawaz Mar 01 '16 at 08:50
  • @Nawaz I love C++ and I understand why it works. But it surprised me. The invariants your class could need are violated by this "feature". Yeah, one could say "no invariants if you don't use this pointer" but I am developing a system where I was asserting a class shared pointer to not be nullptr, the assert work, but the program continued (in debug) and it worked, so the assert(class != nullptr) is useless. But I assume this behavior comes from C. I still love C++, let's improve this in C++17 ;) – LeDYoM Mar 01 '16 at 09:05
  • 1
    LeDYoM: That's not really the idea in C++. The idea is that the compiler is allowed to assume things like "signed integer overflow never happens" and "pointers that are dereferenced have a legal value". If you want to get a crash when you dereference a null pointer, then YOU should assert that it is valid explicitly. Some programs can go many times faster if they don't branch unnecessarily on things like this -- in C++ the idea is that you don't pay for what you don't use, and most programmers don't want to pay this, so removing this particular undefined behavior in C++17 is not likely. – Chris Beck Mar 01 '16 at 09:36
  • @Chris Beck I suppose that's why we love C++. After reading all answers and comments I told me: damm, it completely has sense. But the first time I saw it (I have to say I thought about blaming Microsoft only) I was like wtf is this shit. – LeDYoM Mar 01 '16 at 10:27
  • @LeDYoM: oh, I just reread your comment. So you were asserting it. Yes, `assert` works strangely on windows, they think that you might want to continue after an assertion failure. On linux and mac, it's not possible and assert fail is always no return. What I do is, don't use assert from ``, instead I write a macro `ASSERT` that tests a condition and calls `std::abort` if it fails. Since that works the same way on all platforms. IMO trying to proceed after an assert is a senseless microsoftism, it's better to have cross-platform behavior. (Most people just live with it.) – Chris Beck Mar 01 '16 at 10:34
  • @Chris Beck: Forget about the assert. I was using my own one ;) Anyway, I know what you say is true. But I was kind of playing ;) – LeDYoM Mar 01 '16 at 13:48

3 Answers3

4

Is that expected behavior?

No, it's UB, anything is possible.

Is it because getData() does not use "this"?

Yes, it might work because this won't be used in the special case, but nothing is guaranteed.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
3

It is undefined behavior, so you really should be very scared.

It might happen to apparently do something (on your implementation of C++), because the getData function is not virtual and don't use any member of B. So the generated code does not dereference the null pointer.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
1

It is undefined behaviour, you are accessing a member method from a nullptr. No particular behaviour or outcome would be defined.

In this case though; given (cppreference):

The keyword this is a prvalue expression whose value is the address of the object, on which the member function is being called.

Since the NULL value of this is not being dereference - the member method is not virtual and doesn't access any member data of this - it "seems" to work.

Niall
  • 30,036
  • 10
  • 99
  • 142
  • Are there any conditions you know of that could make the behavior defined, e.g. constexpr/inline/const/noexcept `getData()`, an aggregate/POD/empty class, etc.? Any insight you have would be appreciated. – John P Oct 12 '20 at 15:11
  • Only one I can think of is `static` since it does away with the `this` pointer. – Niall Oct 12 '20 at 16:25
  • I guess I'm thinking of things I've seen in en.cppreference.com/w/cpp/language/expressions#Discarded-value_expressions, and the related pages on prvalues and temporary materialization. But there's more discussion [here](https://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-behav) to support what you said. Personally I get by with `std::declval` when I need a hypothetical object and essentially never use nullptr, but I might need to account for them anyway. Thanks again. – John P Oct 13 '20 at 10:56