一、函數名稱修飾規則
函數的名字修飾(Decorated Name)就是編譯器在編譯期間創建的一個字符串,用來指明函數的定義或原型。LINK程序或其他工具有時需要指定函數的名字修飾來定位函數的正確位置。由於c語言不支持函數重載,而c++語言支持函數重載,所以c和c++的函數名稱修飾規則是不相同的。
以下面這個Add函數爲例,來探討一下c和c++語言的函數名稱修飾規則。
- int Add(int x, int y)
- {
- return x+y;
- }
c語言的函數名稱修飾規則:
[window系統下vs2010]:(在映射文件.map中可以查看,需要右擊當前工程 --〉屬性--〉配置屬性--〉鏈接器 --〉調試 --〉生成映射文件 修改爲 “是 (/MAP)” ,然後運行程序在當前工程的Debug文件夾中就會生成一個.map的文件)。
c語言將上面的函數名稱處理爲_Add,即其修飾規則爲_函數名(函數名前加_)。
[Centos]:(利用objdump命令可以查看目標文件中的函數名字的修飾)。
c語言將上面的函數名稱處理爲Add,即爲函數名。
c++語言的函數名稱修飾規則:
[window系統下vs2010]:
c++語言將上面的函數名稱處理爲?Add@@YAHHH@Z (這個叫做函數名壓扎)
(1)其中‘?’標識函數名的開始,其後跟函數名;
(2)“@@YA”標識參數表的開始,其後跟的第一個字符代表函數的返回值類型,接下來的字符依次代表函數參數列表中各個參數的類型;
類型的代號:
x--void ,
d--char,
e--unsigned char,
f--short,
h--int,
i--unsigned int,
j--long,
k--unsigned long,
m--float,
n--double,
_n--bool,
... ...
所以@@yg後面的HHH分別代表三個int類型,其中第一個爲函數返回值,上下兩個爲參數類型。
(3)@z標識整個名字結束。
[Centos]:
c++語言將上面的函數名稱處理爲_Z3Addii。
其中_Z代表開始,3代表函數名的字符個數,其後跟函數名(這裏爲Add),再加上參數類型(這裏ii代表有兩個整型的參數)。
通過上面的分析,我們發現不同的編譯器對函數名稱修飾的規則是不同的,但我們依然可以找到規律,c語言對函數名稱修飾的處理只關注到了函數名;而c++語言除了函數名,還關注了函數的參數,通過對函數名稱的修飾不同,編譯器調用函數時所找的符號就不同,因而c++語言支持函數重載。也可得出構成函數重載的條件爲函數名相同,參數不同,返回值類型可同可不同。
二、extern "C" 的作用
那麼,就有一個問題,在c++文件中可以直接利用extern使用c文件中的代碼嗎?
Add.c
- int Add(int x, int y)
- {
- return x+y;
- }
Test.cpp
- #include <stdio.h>
- #include <stdlib.h>
- extern int Add(int x, int y);
- int main()
- {
- int z = Add(1, 2);
- printf("%d\n", z);
- system("pause");
- return 0;
- }
上面這樣使用可以嗎? 哎呦,壞了,出現了這個錯誤:error LNK2019: 無法解析的外部符號 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z),該符號在函數 _main 中被引用。
我們來分析一下,Add函數在Add.c這個文件中,編譯時Add函數被處理爲_Add。在Test.cpp文件中我們告訴編譯器我們在別的文件中定義了Add函數,但是Test.cpp這個文件將Add函數處理爲?Add@@YAHHH@Z,所以當買你函數中調用到Add函數時編譯器就在別的文件中一直找?Add@@YAHHH@Z,發現沒有找到。其實在別的文件中Add函數被處理爲了_Add,所以肯定找不到了,長的都不一樣嘛!!
那麼,爲了讓編譯器能夠找到.c文件中的函數,我們該怎麼辦呢??
拿出我們的法寶extern "C",就是將Test.c文件中extern int Add(int x, int y);變爲extern "C" int Add(int x, int y);
使用extern "C" 相當於告訴編譯器這部分代碼使用c語言的規則進行編譯和鏈接。這樣cpp文件中的Add函數就會被處理爲_Add,編譯器就能在Add.c文件中找到它了。