天天看點

extern 和 extern "C"

兩者關系

表面上看extern 和 extern "C"差别不大,後者隻比前者多了一個 “C” 而已;但實際上兩者的用途差了十萬八千裡;

extern

extern 是用來進行外部聲明的。

  • 當我們需要使用在其它檔案中定義的全局變量或全局函數時,需要先用extern進行外部聲明,然後才能在目前檔案中使用該全局變量或全局函數;
//a.cpp file
#include <iostream>
using namespace std;
int num = 20;	//全局變量
void test(){	//全局函數
    cout << num << endl;
}
//b.cpp file
extern int num;
extern void test();
int main(){
    test();
    num = 100;
    test();
    return 0;
}
           

extern “C”

要談 extern “C” 就必須要知道什麼是 命名傾軋(name mangling) !

  • c++支援重載,那麼重載是怎麼實作的,沒錯,實作原理就是這個叫做命名傾軋的東西;
  • 在編譯的時候,c++聲明的函數的函數名會根據原函數名和參數清單被重命名,使得原來具有相同函數名,但具有不同參數清單的函數具有了不同的函數名(表面上說的是c語言不允許同名函數存在,而c++允許同名函數存在,本質上還是不允許的嘛!),這個就叫做命名傾軋;

示例:

//傾軋前
void funcA(int a);
void funcA(char b);
void funcB(string c);
void funcA(int a){
    ...
}
void funcA(char b){
    ...
}
void funcB(string c){
    ...
}
int main(){
    int a = 10;
    char b = 'm';
    string c = "123";
    funcA(a);
    funcA(b);
    funcB(c);
    return 0;
}
//傾軋後
void funcA_i(int a);
void funcA_c(char b);
void funcB_str(string c);
void funcA_i(int a){
    ...
}
void funcA_c(char b){
    ...
}
void funcB_str(string c){
    ...
}
int main(){
    int a = 10;
    char b = 'm';
    string c = "123";
    funcA_i(a);
    funcA_c(b);
    funcB_str(c);
    return 0;
}
//大概就是上面的意思,新的函數名隻是示意一下,實際上并不是那樣 : )
//編譯時,C++編譯器會對函數聲明、函數定義、函數調用三部分統一進行傾軋(腦補一下不同時傾軋的場景);
//無論函數是否重載,都會被C++編譯器傾軋(funcB沒有重載,但也被傾軋了);
           

那麼傾軋跟 extern “C” 又有什麼關系呢?

  • 對于C++要相容C的問題,C++就必須相容C的文法與C庫(連結庫),C庫隻在連結時加入,傾軋在編譯階段發生,是以C庫不會被傾軋;
  • 然而,C函數聲明、C函數調用會在編譯時被傾軋,導緻C函數聲明、C函數調用與C庫中函數對應不上,這就是問題所在;
  • 解決辦法就是:禁止C函數聲明、C函數調用被傾軋;
  • 是以,提出了extern “C”,禁止C++編譯器對其所包含的部分進行傾軋(是以,把extern “C” 叫做 拒絕傾軋 可還行,哈哈);

結論:

  • 如果要在C++工程中使用已編譯好的C庫,C的所有函數聲明必須被 extern “C” 包圍;
  • 傾軋是C++為了實作函數重載而設計的,拒絕傾軋的 extern “C” 則是為了相容C而後實作的,我們程式設計一般犯不着對自定義的C++函數設定;

疑問:

  • 那為啥不把C庫用C++編譯器重新編譯下,這樣就不用通過 extern “C” 禁止傾軋了?
  • 猜想:隻有大家傾軋實作一模一樣時,上述辦法才有意義;大家傾軋實作略有差别,難不成每種傾軋實作對應一個C庫,這樣顯然不合适;

參考文獻

C++函數重載與重載原理:命名傾軋