今天遇到一個Access Violation的crash,只看crash call stack沒有找到更多的線索,於是在debug模式下又跑了一遍,遇到了如下的一個debug的錯誤提示框:
這個是什么原因呢?我們來看一個簡單的例子來重現這個錯誤。
假設我們有2個父類,分別是BaseA和BaseB。有一個子類Child,繼承自BaseA和BaseB。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class BaseA { public: virtual void foo()=0; }; class BaseB { public: virtual void bar(int a)=0; }; class Child: public BaseA, public BaseB { public: void foo() { cout<<"i'm foo in Child!"<<endl; }; void bar(int a) { cout<<"i'm bar in Child!"<<endl; }; }; |
假設我們有如下的main函數:
1 2 3 4 5 6 7 8 9 10 |
int main() { BaseB* b = new Child(); BaseA* a = (BaseA*)b; BaseA* a2 = dynamic_cast<BaseA*> (b); // This is actually calling bar(), // and will cause Runtime check failure about ESP if the foo and bar have different signature. a->foo(); a2->foo(); } |
在這個main函數里a是通過C-Style的強轉轉來的,a2是通過dynamic_cast轉換來的。如果我們運行這個程序,在release下就會報access violation的crash,在debug下就會出上面列的ESP錯誤的對話框。
函數的輸出如下圖所示:
這就能解釋為什么會出這個ESP的錯誤了,因為我們想調用foo,但是實際上調用的bar,但是foo和bar的函數簽名不一樣,所以導致了出現那個debug版本下的ESP的錯誤提示。但是加入我們把bar(int a)中的參數去掉,這樣foo和bar就有一樣的函數簽名了,這樣及時在debug版本下也不會出這個ESP的提示,在release或者debug模式下都不會報任何錯誤,但是我們在runtime調用了錯誤的函數!!!
可以看看下圖回憶一下vtable是怎么回事:)
也可以參考怎么看C++對象的內存結構 和 怎么解密C++的name mangling - fresky - 博客園來看看C++對象的內存結構。
對於ESP的錯誤,可以參考stackoverflow上的這個問題:c++ - Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call - Stack Overflow