I was experimenting with C++ and found the below code as very strange.
class Foo{
public:
virtual void say_virtual_hi(){
std::cout << "Virtual Hi";
}
void say_hi()
{
std::cout << "Hi";
}
};
int main(int argc, char** argv)
{
Foo* foo = 0;
foo->say_hi(); // works well
foo->say_virtual_hi(); // will crash the app
return 0;
}
I know virtual method call crashes because it requires a vtable lookup and can work only with valid objects.
I have the following questions
- How the non virtual method *say_hi* works on a NULL pointer?
- Where does the object foo gets allocated?
Any thoughts?
The object foo
is a local variable with type Foo*
. That variable likely gets allocated on the stack for the main
function, just like any other local variable. But the value stored in foo
is a null pointer. It doesn't point anywhere. There is no instance of type Foo
represented anywhere.
To call a virtual function, the caller needs to know which object the function is being called on. That's because the object itself is what tells which function should really be called. (That's frequently implemented by giving the object a pointer to a vtable, a list of function-pointers, and the caller just knows it's supposed to call the first function on the list, without knowing in advance where that pointer points.)
But to call a non-virtual function, the caller doesn't need to know all that. The compiler knows exactly which function will get called, so it can generate a CALL
machine-code instruction to go directly to the desired function. It simply passes a pointer to the object the function was called on as a hidden parameter to the function. In other words, the compiler translates your function call into this:
void Foo_say_hi(Foo* this);
Foo_say_hi(foo);
Now, since the implementation of that function never makes reference to any members of the object pointed to by its this
argument, you effectively dodge the bullet of dereferencing a null pointer because you never dereference one.
Formally, calling any function — even a non-virtual one — on a null pointer is undefined behavior. One of the allowed results of undefined behavior is that your code appears to run exactly as you intended. You shouldn't rely on that, although you will sometimes find libraries from your compiler vendor that do rely on that. But the compiler vendor has the advantage of being able to add further definition to what would otherwise be undefined behavior. Don't do it yourself.
The say_hi() member function is usually implemented by the compiler as
void say_hi(Foo *this);
Since you don't access any members, your call succeeds (even though you are entering undefined behaviour according to the standard).
Foo doesn't get allocated at all.
Dereferencing a NULL pointer causes "undefined behaviour", This means that anything could happen - your code may even appear to work correctly. You must not depend on this however - if you run the same code on a different platform (or even possibly on the same platform) it will probably crash.
In your code there is no Foo object, only a pointer which is initalised with the value NULL.
It is undefined behaviour. But most of compilers made instructions which will handle this situation correctly if you don't accessing to member variables and virtual table.
let see disassembly in visual studio for understand what happens
Foo* foo = 0;
004114BE mov dword ptr [foo],0
foo->say_hi(); // works well
004114C5 mov ecx,dword ptr [foo]
004114C8 call Foo::say_hi (411091h)
foo->say_virtual_hi(); // will crash the app
004114CD mov eax,dword ptr [foo]
004114D0 mov edx,dword ptr [eax]
004114D2 mov esi,esp
004114D4 mov ecx,dword ptr [foo]
004114D7 mov eax,dword ptr [edx]
004114D9 call eax
as you can see Foo:say_hi called as usual function but with this in ecx register. For simplify you can assume that this passed as implicit parameter which we never use in your example.
But in second case we calculating adress of function due virtual table - due foo addres and gets core.
a) It works because it does not dereference anything through the implicit "this" pointer. As soon as you do that, boom. I'm not 100% sure, but I think null pointer dereferences are done by RW protecting first 1K of memory space, so there is a small chance of nullreferencing not getting caught if you only dereference it past 1K line (ie. some instance variable that would get allocated very far, like:
class A {
char foo[2048];
int i;
}
then a->i would possibly be uncaught when A is null.
b) Nowhere, you only declared a pointer, which is allocated on main():s stack.
The call to say_hi is statically bound. So the computer actually simply does a standard call to a function. The function doesn't use any fields, so there is no problem.
The call to virtual_say_hi is dynamically bound, so the processor goes to the virtual table, and since there is no virtual table there, it jumps somewhere random and crashes the program.
In the original days of C++, the C++ code was converted to C. Object methods are converted to non-object methods like this (in your case):
foo_say_hi(Foo* thisPtr, /* other args */)
{
}
Of course, the name foo_say_hi is simplified. For more details, look up C++ name mangling.
As you can see, if the thisPtr is never dereferenced, then the code is fine and succeeds. In your case, no instance variables or anything that depends on the thisPtr was used.
However, virtual functions are different. There's a lot of object lookups to make sure the right object pointer is passed as the paramter to the function. This will dereference the thisPtr and cause the exception.
I was experimenting with C++ and found the below code as very strange.
class Foo{
public:
virtual void say_virtual_hi(){
std::cout << "Virtual Hi";
}
void say_hi()
{
std::cout << "Hi";
}
};
int main(int argc, char** argv)
{
Foo* foo = 0;
foo->say_hi(); // works well
foo->say_virtual_hi(); // will crash the app
return 0;
}
I know virtual method call crashes because it requires a vtable lookup and can work only with valid objects.
I have the following questions
- How the non virtual method *say_hi* works on a NULL pointer?
- Where does the object foo gets allocated?
Any thoughts?
The object foo
is a local variable with type Foo*
. That variable likely gets allocated on the stack for the main
function, just like any other local variable. But the value stored in foo
is a null pointer. It doesn't point anywhere. There is no instance of type Foo
represented anywhere.
To call a virtual function, the caller needs to know which object the function is being called on. That's because the object itself is what tells which function should really be called. (That's frequently implemented by giving the object a pointer to a vtable, a list of function-pointers, and the caller just knows it's supposed to call the first function on the list, without knowing in advance where that pointer points.)
But to call a non-virtual function, the caller doesn't need to know all that. The compiler knows exactly which function will get called, so it can generate a CALL
machine-code instruction to go directly to the desired function. It simply passes a pointer to the object the function was called on as a hidden parameter to the function. In other words, the compiler translates your function call into this:
void Foo_say_hi(Foo* this);
Foo_say_hi(foo);
Now, since the implementation of that function never makes reference to any members of the object pointed to by its this
argument, you effectively dodge the bullet of dereferencing a null pointer because you never dereference one.
Formally, calling any function — even a non-virtual one — on a null pointer is undefined behavior. One of the allowed results of undefined behavior is that your code appears to run exactly as you intended. You shouldn't rely on that, although you will sometimes find libraries from your compiler vendor that do rely on that. But the compiler vendor has the advantage of being able to add further definition to what would otherwise be undefined behavior. Don't do it yourself.
The say_hi() member function is usually implemented by the compiler as
void say_hi(Foo *this);
Since you don't access any members, your call succeeds (even though you are entering undefined behaviour according to the standard).
Foo doesn't get allocated at all.
Dereferencing a NULL pointer causes "undefined behaviour", This means that anything could happen - your code may even appear to work correctly. You must not depend on this however - if you run the same code on a different platform (or even possibly on the same platform) it will probably crash.
In your code there is no Foo object, only a pointer which is initalised with the value NULL.
It is undefined behaviour. But most of compilers made instructions which will handle this situation correctly if you don't accessing to member variables and virtual table.
let see disassembly in visual studio for understand what happens
Foo* foo = 0;
004114BE mov dword ptr [foo],0
foo->say_hi(); // works well
004114C5 mov ecx,dword ptr [foo]
004114C8 call Foo::say_hi (411091h)
foo->say_virtual_hi(); // will crash the app
004114CD mov eax,dword ptr [foo]
004114D0 mov edx,dword ptr [eax]
004114D2 mov esi,esp
004114D4 mov ecx,dword ptr [foo]
004114D7 mov eax,dword ptr [edx]
004114D9 call eax
as you can see Foo:say_hi called as usual function but with this in ecx register. For simplify you can assume that this passed as implicit parameter which we never use in your example.
But in second case we calculating adress of function due virtual table - due foo addres and gets core.
a) It works because it does not dereference anything through the implicit "this" pointer. As soon as you do that, boom. I'm not 100% sure, but I think null pointer dereferences are done by RW protecting first 1K of memory space, so there is a small chance of nullreferencing not getting caught if you only dereference it past 1K line (ie. some instance variable that would get allocated very far, like:
class A {
char foo[2048];
int i;
}
then a->i would possibly be uncaught when A is null.
b) Nowhere, you only declared a pointer, which is allocated on main():s stack.
The call to say_hi is statically bound. So the computer actually simply does a standard call to a function. The function doesn't use any fields, so there is no problem.
The call to virtual_say_hi is dynamically bound, so the processor goes to the virtual table, and since there is no virtual table there, it jumps somewhere random and crashes the program.
In the original days of C++, the C++ code was converted to C. Object methods are converted to non-object methods like this (in your case):
foo_say_hi(Foo* thisPtr, /* other args */)
{
}
Of course, the name foo_say_hi is simplified. For more details, look up C++ name mangling.
As you can see, if the thisPtr is never dereferenced, then the code is fine and succeeds. In your case, no instance variables or anything that depends on the thisPtr was used.
However, virtual functions are different. There's a lot of object lookups to make sure the right object pointer is passed as the paramter to the function. This will dereference the thisPtr and cause the exception.
0 commentaires:
Enregistrer un commentaire