C++過載底層原理

牛犁heart發表於2023-03-05

好吧,承認是自己淺薄了
當被問起C++過載時,嘴角不自覺的微微上揚,然後脫口而出,C++過載的原則:

  • 函式名相同,函式引數列表不同(型別、個數、順序)
  • 匹配原則1:嚴格匹配,找到再呼叫
  • 匹配原則2:透過隱式型別轉換尋求一個匹配,找到則呼叫
  • 注:返回型別不構成過載條件

C++編譯時多型也是由過載函式來實現的,那既然扯到多型了,順便也把執行時多型(虛擬函式)相關的東西簡單了說了下

結果誰成想,反手就問了C++過載的底層實現原理是怎樣的?

這。。。瞬間矇蔽

或者問:為什麼C沒有過載,C++有過載


------不華麗的分割線------


先說結論:
C++針對函式名有經過一種叫Name Mangling的特殊處理,網上很多都是翻譯成了命名傾軋
成員函式的函式名會經過Name Mangling處理,得到一個程式中獨一無二的詞彙。

  • Name Mangling對成員變數的處理,一般會在變數名稱前加上類名稱,形成獨一無二的命名。
    舉例:
class Bar{public: int ival;...}

其中的ival有可能變成:

ival_3Bar

PS:這個結果,可能會因為編譯器的編碼方法不同而不同。

  • 針對成員函式,為讓它們獨一無二,唯有再加上它們的引數列表
    舉例:
class Point{
public:
	void x(float newX,int newY);
	void x(int newY, float newX);
	float x();
	...
}

它可能轉換為:

class Point{
public:
	void x_5PointFfi(float newX, int newY);
	void x_5PointFif(int newY, float newX);
	float x_5PointFv();
	...

}

這也就解釋了為什麼C++過載對引數型別、順序、數量作為過載的原則。

至於C為什麼不能過載,那是因為編譯器只是對函式名做了獨一無二的命名處理,並沒有帶上引數相關的資訊。

另:
如果宣告瞭extern "C",就會禁止命名傾軋name mangling的效果。


------不華麗的分割線------


一個完整的C++編譯過程(例如g++ a.cpp生成可執行檔案),總共包含以下四個過程:

  • 編譯預處理,也稱預編譯,可以使用命令g++ -E執行
  • 編譯,可以使用g++ -S執行
  • 彙編,可以使用as 或者g++ -c執行
  • 連結,可以使用g++ xxx.o xxx.so xxx.a執行
#  -E 編譯器對檔案進行預處理
g++ -E test.cpp -o test.i     //i檔案
#  -S編譯器告訴g++再為c++程式碼產生組合語言後停止編譯
g++ -S test.i -o test.s    
#  -c 選項告訴g++僅把原始碼編譯為機器語言的目的碼
g++ -c test.s -o test.o    (-c小寫)
#    -0 產生可執行檔名

g++ test.0 -o test
寫程式碼來看下:
image

透過g++ -c會將原始碼編譯成機器語言的目的碼,然後使用objdump -t 目標檔案將二進位制檔案進行反彙編,具體如下:
image
其中,_Z是規定字首,4是函式名的字元個數,i是引數列表型別i的首字母

C++也提供了命名反傾軋
1.將名字改編轉化成函式名
使用c++filt命令可以很容易把名字改編轉換成函式名

c++filt _Z4funci
  1. 檢視反傾軋的符號表
    有兩種方式:
  • nm -C 目標檔案

  • objdump -t -C 目標檔案
    結果如下:
    image

可參考:
絕對強大的三個linux指令: ar, nm, objdump

相關文章