最近幫同事排查了一個崩潰問題,用他的話說是一個「神奇的」崩潰問題。
這個問題大概是這樣的。
同事在 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 去訪問虛函數表必然引起非法訪問,從而導致程序崩潰。
(完)