In general, the layout of a object instantiated from a class is as follows:
* - v_ptr ---> * pTypeInfo
| |- pVirtualFuncA
| |- pVirtualFuncB
|- MemberVariableA
|- MemberVariableB
v_ptr
is a pointer to the v-table - which contains addresses of virtual functions and RTTI data for the object. Classes without virtual functions don't have v-tables and the corresponding objects don't have v_ptr
.
In your example above, class A
has no virtual methods and thus no v-table. This means the implementation of sayHi()
to call can be determined at compile time and is invariant.
The compiler generates code that sets the implicit this
pointer to a
and then jumps to the beginning of sayHi()
. Since the implementation has no need for the object's contents, the fact that it works when the pointer is NULL
is a happy coincidence.
If you were to make sayHi()
virtual, the compiler cannot determine the implementation to call at compiler time, so instead generates code that looks up the address of the function in the v-table and calls it. In your example where a
is NULL
, the compiler reads the contents of address 0
, causing the abort.