1. 程式人生 > >C++函式編譯原理和成員函式的實現

C++函式編譯原理和成員函式的實現

轉載自:http://c.biancheng.net/cpp/biancheng/view/2996.html點選開啟連結

從上節的例子可以看出,物件的記憶體模型中只保留了成員變數,除此之外沒有任何其他資訊,程式執行時不知道 obj 的型別為 Demo,也不知道它還有一個成員函式 display()。那麼,究竟是如何通過物件呼叫成員函式的呢?

C++函式的編譯

C++和C語言的編譯方式不同。C語言中的函式在編譯時名字不變,或者只是簡單的加一個下劃線_(不同的編譯器有不同的實現),例如,func() 編譯後為 func() 或 _func()。

而C++中的函式在編譯時會根據名稱空間、類、引數簽名等資訊進行重新命名,形成新的函式名。這個重新命名的過程是通過一個特殊的演算法來實現的,稱為名字編碼(Name Mangling)


Name Mangling 是一種可逆的演算法,既可以通過現有函式名計算出新函式名,也可以通過新函式名逆向推演出原有函式名。

Name Mangling 可以確保新函式名的唯一性,只要名稱空間、所屬的類、引數簽名等有一個不同,那麼產生的新函式名也不同。

如果你希望看到經演算法產生的新函式名,可以只宣告而不定義函式,這樣呼叫函式時就會產生連結錯誤,從報錯資訊中就可以看到。請看下面的程式碼:
    #include<iostream>
    using namespace std;

    void display();
    void display(int);

    namespace ns{
        void display();
    }

    class Demo{
    public:
        void display();
    };

    int main(){
        display();
        display(1);
        ns::display();
        Demo obj;
        obj.display();

        return 0;
    }


該例中聲明瞭四個同名函式,包括兩個具有過載關係的全域性函式,一個位於名稱空間 ns 下的函式,以及一個屬於類 Demo 的函式。它們都是隻宣告而未定義的函式。

編譯原始碼即可看到錯誤資訊:


小括號中就是 Name Mangling 產生的新函式名,它們都以”?“開始,以區別C語言中的”_“。

上圖是VS2010產生的錯誤資訊,不同的編譯器有不同的 Name Mangling 演算法,產生的函式名也不一樣。
__thiscall、cdecl 是函式呼叫方式,有興趣的讀者可以猛擊《函式的幾種呼叫方式》一文深入瞭解。
除了函式,某些變數也會經 Name Mangling 演算法產生新名字,不再贅述。

成員函式的呼叫

從上圖可以看出,成員函式最終被編譯成與物件無關的普通函式,如果函式體中沒有成員變數,那問題就很簡單,不用對函式做任何處理,直接呼叫即可。

如果成員函式中使用到了成員變數,該怎麼辦呢?成員變數的作用域不是全域性,不經任何處理就無法在函式內部訪問。

C++規定,編譯成員函式時要額外新增一個引數,把當前物件的指標傳遞進去,通過指標來訪問成員變數。

假設 Demo 類有兩個 int 型的成員變數,分別是 a 和 b,並且在成員函式 display() 中使用到了,如下所示:
    void Demo::display(){
        cout<<a<<endl;
        cout<<b<<endl;
    }


那麼編譯後的形式類似於:
    void new_function_name(const Demo *p){
        //通過指標p來訪問a、b
        cout<<p->a<<endl;
        cout<<p->b<<endl;
    }


呼叫時的形式類似於:
new_function_name(&obj);


這樣就完成了物件和成員函式的關聯,只不過與我們從表明上看到的相反,不是通過物件找函式,而是通過函式找物件。

這一切都是隱式完成的,對程式設計師來說完全透明,就好像這個額外的引數不存在一樣。