最近帮同事排查了一个崩溃问题,用他的话说是一个「神奇的」崩溃问题。
这个问题大概是这样的。
同事在 A 类写了一个函数 doSomething,然后在某个地方调用。
代码简化如下:
class A
{
public:
void doSomething()
{
printf("class A doSomething! \n");
}
};
A *a;
a->doSomething();
是的,一切正常。
然后由于 A 类是基类,而这个函数其他子类可能需要重载实现。于是,就加上了 virtual 关键字,看样子一切正常。
class A
{
public:
virtual void doSomething()
{
printf("class A doSomething! \n");
}
};
A *a;
a->doSomething();
「神奇的」事情发生了,程序崩溃在了调用 doSomething 的地方。
同事陷入了深深的迷茫中。。。
经过一番 debug,真想终于水落石出。原因是对象 a 在调用 doSomething 的时候已经被其他地方赋为了空指针。
既然 a 是空指针,那么为什么不加 virtual 关键字的时候,调用 doSomething 就一切正常呢?
这是因为:
在 C++ 里,非虚函数的地址是在编译\链接完成时就已经确定了。因此当运行到 a->doSomething() 这句时,是可以找到该函数地址,从而执行相应代码的。但是需要注意的是,不能在这个函数里使用 A 的成员变量,因为 this 指针的地址是非法的。
而虚函数一般是通过虚函数表来实现的,在这个表中存的主要就是该类的虚函数的地址。虚函数表的创建是在对象实例化时完成的,也就是说虚函数的地址是在程序运行时才能确定。因此当运行到 a->doSomething() 这句时,由于 doSomething 是虚函数,而此时 a 对象又为空,用 a 去访问虚函数表必然引起非法访问,从而导致程序崩溃。
(完)