-2

While writing my first piece of software, I encountered a "strange" behavior when playing with virtual functions, inheritance, and pointers. What I had was a template class State, that had a following function in the public interface

void State::saveState() {};

When the app was first fired up, in order to set a default state, it was calling a setState() function within the manager function, that roughly goes as follows

case(some enum):
{
    mActiveState->saveState();
    delete mActiveState; 
    mActiveState = new SomeStateClassDerivedFromState();
    mActiveState->setup(some arguments);
    break;
}

Now for the strange thing - the first two lines of this code should cause a run-time error, or something similar. If this function is used to CHANGE the state, then it should be fine. But it was also used for the assignment of some object/memory for State::mActiveState, which at that point is nullptr. But it didn't crash for some reason, program executed as expected. But then, I wanted to override the saveState() function in one of the classes inheriting from State, so I added a virtual tag to the function (body remained empty), and wrote a definition for saveState() in the derived class. Suddenly, these two lines started crashing the app at startup (as expected). Removing the virtual tag from saveState() made the app run again.

Why didn't calling a member function and deleting a nullptr cause a crash until the saveState() member function was declared as virtual (with empty body, not pure virtual)?

This code recreates the problem

class State
{
public:
void saveState() {};
}

class DState : public State
{
saveState() {};
}

int main()
{
State* state = nullptr;
state->saveState();
delete state;
state = new DState();
}

This worked. The moment I added virtual tag to the saveState() function in the State class, it crashed on start. My question again, why?

Kendra
  • 769
  • 1
  • 20
  • 34
Quit
  • 22
  • 6
  • 1
    Provide a [MCVE] demonstrating the problem please. – πάντα ῥεῖ Mar 09 '16 at 19:49
  • 2
    Deleting a null pointer should not cause a crash. – Logicrat Mar 09 '16 at 19:50
  • Where did you put the `virtual` keyword. In the base class definition or higher up? – kfsone Mar 09 '16 at 19:57
  • Possible duplicate of [Why does calling method through null pointer "work" in C++?](http://stackoverflow.com/questions/11320822/why-does-calling-method-through-null-pointer-work-in-c) – Jean-Baptiste Yunès Mar 09 '16 at 20:12
  • @Jean-Baptiste Yunès how is the problem of virtual/non-virtual function making a difference between how the app runs a duplicate of the question you have linked again? – Quit Mar 09 '16 at 20:24
  • Because with a virtual method, another mechanism is involved that needs to dereference the (here null) pointer to retrieve the right function to call... With non virtual you basically have something like a call to `saveState(nullptr)` while with a virtual method you would have `m_ptr=find_method_in(nullptr,SAVESTATEID); m_ptr(nullptr);` and the *first* expression need to examine the structure passed as first parameter... – Jean-Baptiste Yunès Mar 09 '16 at 20:32

2 Answers2

3
class State
{
public:
    void saveState() {};
}
//...
    State* state = nullptr;
    state->saveState();
//...

Because saveState is non-virtual here, you are simply calling a function that quietly takes a this pointer as the first argument.

The function does nothing, so nothing happens, infact the compiler may even be able to elide the function and the call to it entirely. If your function had tried to reference a member variable, it would have crashed. You've basically invoked Undefined Behavior and gotten lucky - mostly because you did nothing in the function.

When you make the function virtual, the program has to dereference the object to find it's vtable (virtual function table) which means it dereferences a nullptr and that's why you get a crash.

You also asked why deleting a null pointer doesn't crash: that's because, unlike free, it is legal to delete a null pointer -- basically delete does the check for you.

kfsone
  • 23,617
  • 2
  • 42
  • 74
1

Calling an empty method through nullptr has undefined behaviour but does not have to crash your program, as no data is accessed. Calling virtual function though requires to look for vtable, which brings behavior you see. But even if your program did not crash it does not make it valid.

On another side deleting nullptr is valid and leads to no-op.

Slava
  • 43,454
  • 1
  • 47
  • 90