Lets say we have a class
class A
{
int x;
public:
void sayHi()
{
cout<<"Hi";
}
};
int main()
{
A *a=NULL;
a->sayHi();
}
The above code will compile on Turbo C (where I tested) and print Hi
as output.
I was expecting crash because a
is NULL
. More over if I make sayHi()
function virtual, it says
Abnormal temination(Segmentation fault in gcc)
I know a lot of it is implementation dependent but if anybody could throw some light on any implementation or just give an overview it would be really nice.
Each time a function is called, the address of where to return to and certain information about the caller's environment, such as some of the machine registers, are saved on the stack. The newly called function then allocates room on the stack for its automatic variables. This is how recursive functions in C can work.
This is because in machine code, a function is referenced by its location in RAM, not its name. The compiler-output object file may have a func entry in its symbol table referring to this block of machine code, but the symbol table is read by software, not something the CPU hardware can decode and run directly.
In the C Programming Language, the memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1. It returns a pointer to the destination. The memcpy function may not work if the objects overlap.
As a generalisation, the layout of a object instantiated from a class with no super classes and virtual functions 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.
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.
In C++, the methods of a class are not stored inside the instances of that class. They're simply some "special" functions that transparently accept the this
pointer in addition to the arguments specified by the programmer.
In your case, the sayHi()
method does not reference any of the class fields, therefore, the this
pointer (which is NULL
) is never followed.
Make no mistake, though, this is still undefined behavior. Your program may choose to send nasty emails to your contact list when you invoke this. In this particular instance, it does the worst thing, and appears to work.
The virtual
method case has been added since I answered the question, but I won't refine my answer, since it's included by others' answers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With