只要学了C++的人,肯定知道静态联编和动态联编,如果你不知道,ok那你学习之路还长。简单的静态联编的东西就不说了。先看下面程序。
class AA{
public:
void result()
{
std::cout << "Surprise?" << std::endl;
};
};
int main()
{
AA *p = NULL;
p->result();
system("Pause");
return 0;
}
上面程序运行会报错吗?
——————————————————————
如果你说运行一切正常并知道原因,ok。那就别往下看了,时间就是金钱。
确实,这个运行正常并输出 Surprise? 不信?你copy过去运行下试试。为啥啊。明明指针p的值是NULL,而你使用NULL指针去调用成员函数,明明会报内存错误的瑟。书上不是说了不能使用 NULL指针吗?嘿嘿,没错,确实不能使用NULL指针,但是这里,程序根本就没有用指针p的值,而是仅仅用到了它的类型做静态束定而已。
要解此题首先要明确两个问题。
1、静态联编的原理;2、成员函数的代码在运行期只有一份拷贝。
静态联编简单的说就是在编译期就已经确定了要调用哪个函数了,这里的result()就是。同时要知道,类的成员函数在运行期只有一份拷贝在内存,不管类的实例有多少个,成员函数始终只有一份代码在内存,因此只要知道类的指针的类型之后,就可以定位到函数的入口地址,根本不关心该指针指向的是一个什么东西。成员函数和成员变量不一样,非静态成员变量是跟随类的实例走的。
ok,明白上面两个问题之后,这个事情就好解决了。直接上汇编吧。
汇编如下:
AA *p = NULL;
00411ACE mov dword ptr [p],0
p->result();
00411AD5 mov ecx,dword ptr [p]
00411AD8 call AA::result (41105Ah)
清楚了吧。在执行p->result()的时候只是把p的值移动到了一个暂存器里面,但是并没有用到这个值,后面就直接调用AA::result函数了,0x41105A正是该函数的入口地址。
ok,好了。不仅可以向以上说的去访问成员函数,甚至再过分一点((A*)0)->result();这样都可以。你再火一点把那个0换成任意一个地址都可以正确调用到那个函数,因为编译器在静态束定的时候只关心那个指针的类型。当然了,不可这样去访问类的成员变量,因为成员变量是在对象的内存布局里面的。
值得说一点的是,如果你在result函数里面有涉及到类的成员变量的访问,那么这显然就会出错了,因为成员变量需要通过传进来的this指针(其实就可以理解成时p指针)去访问对象的内存的。然而此时p还没有指向一个有效的空间。故而出错。