標籤:輸出 void int null 尋找 問題 itblog using 不同
引出:寫個類A,聲明類A指標指向NULL,調用類A的方法會有什麼後果,編譯通過嗎,運行會通過嗎?
#include<stdio.h>#include<iostream>using namespace std;class base{ int a;public: void fun(){ printf("base fun\n"); }};int main(){ base *b=NULL; b->fun();}
看到這個的時候,一定以為運行會報錯吧。
但是奇蹟般的,編譯器輸出了:base fun
#include<stdio.h>#include<iostream>using namespace std;class base{ int a;public: virtual void fun(){ printf("base fun\n"); }};int main(){ base *b=NULL; b->fun();}
在看這個代碼,還以為會輸出base fun麼,又錯了,運行報錯!
為什麼會是這個結果?
#include<stdio.h>#include<iostream>using namespace std;class base{ int a;public: virtual void fun(){ printf("base fun\n"); } void fun2(){ printf("base fun\n"); }};int main(){ base *b=NULL; b->fun(); b->fun2();}
可以發現,一個是虛函數,一個普通函數
在觀察下記憶體中得情況:
發現果然虛函數還沒在記憶體中,而fun2已經在記憶體中了
在看看彙編:
明顯發現虛函數的調用比普通函數多了好幾個步驟,
ecx 中放的this 指標,所以this=0(NULL),但是普通函數fun2放在全域記憶體區,所以可以訪問
而虛函數是根據虛函數表尋找的,這時沒有虛函數表,自然就沒法查到虛函數的地址了。
因為非虛函數的地址對編譯期來說“靜態”的,也就是函數地址在編譯期就已經確定了,執行個體地址對於非虛函數只是那個 this 指標參數。所以只要不訪問類的執行個體資料就沒什麼問題。而虛函數的地址,是先到執行個體的地址前面去尋找它的虛函數表所在的地址。然後從虛函數表裡取出該函數所對應的元素(虛函數表是一個函數指標數組)來call的。(當然一個已知的類的虛函數表的內容也是編譯期靜態,但不同類的虛函數表內容不同,即運行時多態的基礎)所以執行個體如果為NULL,是個有特殊意義的值,是會觸發執行階段錯誤的。
總結:類中的虛函數是動態產生的,由虛函數表的指向進行訪問,不為類的對象分配記憶體,就沒有虛函數表就無法訪問。
類中的普通函數靜態產生,不為類的對象分配記憶體也可訪問。
C++ 普通函數和虛函數調用的區別