C++ 普通函數和虛函數調用的區別


引出:寫個類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,是個有特殊意義的值,是會觸發運行時錯誤的。

 

總結:類中的虛函數是動態生成的,由虛函數表的指向進行訪問,不為類的對象分配內存,就沒有虛函數表就無法訪問。

   類中的普通函數靜態生成,不為類的對象分配內存也可訪問。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM