C++函式編譯原理和成員函式的實現
阿新 • • 發佈:2019-02-15
轉載自: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);
這樣就完成了物件和成員函式的關聯,只不過與我們從表明上看到的相反,不是通過物件找函式,而是通過函式找物件。
這一切都是隱式完成的,對程式設計師來說完全透明,就好像這個額外的引數不存在一樣。