extern “C”
在C++ 程序中調用被C 編譯器編譯后的函數,為什么要加extern “C”?
這個問題包括兩部分。一部分是extern的作用,一個是“C”的作用。
下面將從這兩個部分分別整理下相關知識。
一、extern
1、?聲明外部變量
?現代編譯器一般采用按文件編譯的方式,因此在編譯時,各個文件中定義的全局變量是互相透明的,也就是說,在編譯時,全局變量的可見域限制在文件內部。
我們在A。cpp文件中定義如下句子
int i=0;
在B.cpp文件中定義如下句子
int i=0;
然后分別編譯兩個文件,此時,編譯成功。而當我們將兩個文件進行鏈接時,卻提示錯誤。錯誤提示是error C2086: 'int i' : redefinition。
這就是說,在編譯階段,各個文件中定義的全局變量相互是透明的,編譯A時覺察不到B中也定義了i,同樣,編譯B時覺察不到A中也定義了i。
但是到了鏈接階段,要將各個文件的內容“合為一體”,因此,如果某些文件中定義的全局變量名相同的話,在這個時候就會出現錯誤,也就是上面提示的重復定義的錯誤。
因此,各個文件中定義的全局變量名不可相同。
在鏈接階段,各個文件的內容(實際是編譯產生的obj文件)是被合并到一起的,因而,定義于某文件內的全局變量,在鏈接完成后,它的可見范圍被擴大到了整個程序。
???????這樣一來,按道理說,一個文件中定義的全局變量,可以在整個程序的任何地方被使用,舉例說,如果A文件中定義了某全局變量,那么B文件中應可以用該變量。修改我們的程序,加以驗證:
將B.cpp的代碼改為
i = 100;
此時,編譯出錯,提示i未定義。
?
其實出現這個錯誤是意料之中的,因為:文件中定義的全局變量的可見性擴展到整個程序是在鏈接完成之后,而在編譯階段,他們的可見性仍局限于各自的文件。
?
編譯器的目光不夠長遠,編譯器沒有能夠意識到,某個變量符號雖然不是本文件定義的,但是它可能是在其它的文件中定義的。
?
???????雖然編譯器不夠遠見,但是我們可以給它提示,幫助它來解決上面出現的問題。這就是extern的作用了。
?extern的原理很簡單,就是告訴編譯器:“你現在編譯的文件中,有一個標識符雖然沒有在本文件中定義,但是它是在別的文件中定義的全局變量,你要放行!”
于是,我們在B.cpp文件中,將代碼改為
extern int i;
此時重新編譯,ok,沒問題了。
因此我們可以總結如下:extern是C/C++語言中表明函數和全局變量作用范圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量可以在本模塊或其它模塊中使用。
通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。
二、“C”
2.1 C方式編譯和C++方式編譯
相對于C,C++中新增了諸如重載等新特性,對于他們的編譯,必然有一些重要的區別。根據我們實驗,以及查看的資料,可以很容易就得知這些區別是什么。那就是:
?
作為一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯后在符號庫中的名字與C語言的不同。例如,假設某個函數的原型為:
?
void foo( int x, int y );
?
該函數被C編譯器編譯后在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制,生成的新名字稱為“mangled name”)。
_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,后者為_foo_int_float。
?
同樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理相似,也為類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不同。
因此,我們在文件B.CPP中定義函數void func(),main函數位于文件A.CPP,在main函數中調用了B中定義的函數func()。
?
???????要在A中調用B中定義的函數,必須要加上該函數的聲明。如本例中的void func();就是對函數func()的聲明。
?
如果沒有聲明的話,編譯A.CPP時就會出錯。因為編譯器的目光只局限于被編譯文件,必須通過加入函數聲明來告訴編譯器:“某個函數是定義在其它的文件中的,你要放行!”,這一點跟用extern來聲明外部全局變量是一個道理。
B.h內容為:
int func();
B.cpp內容為:
int func(int i){return 0;}
A.cpp
#include "B.h"
void main()
{ }
此時,以C編譯方式編譯如上代碼,其結果為:
?
PUBLIC????_func
PUBLIC????_main
然后,以C++方式編譯,編譯結果為:
?
PUBLIC?????func@@YAHH@Z?????????????????????????????????????????; func
?
PUBLIC????_main
我們可以發現這兩種編譯方式的不同之處了。
2.2?不同編譯方式下的函數調用
?如果在工程中,不僅有CPP文件,還有以C方式編譯的C文件,函數調用就會有一些微妙之處。我們將B.CPP改作B.C:
對A.CPP和B.C分別編譯,都沒有問題,但是鏈接時出現錯誤。
提示:
Linking...
?
A.obj : error LNK2001: unresolved external symbol "void __cdecl func(void)" (?func@@YAXXZ)
?
Debug/A.exe : fatal error LNK1120: 1 unresolved externals
?
Error executing link.exe.
?
A.exe - 2 error(s), 0 warning(s)
?
原因就在于不同的編譯方式產生的沖突。
?對于文件A,是按照C++的方式進行編譯的,其中的func()調用被編譯成了?
PUBLIC?????func@@YAHH@Z?????????????????????????????????????????; func
?
如果B文件也是按照C++方式編譯的,那么B中的func函數名也會被編譯器改成?func1@@YAXXZ,這樣的話,就沒有任何問題。
???????但是現在對B文件,是按照C方式編譯的,B中的func函數名被改成了_func,這樣一來,A中的call ?func1@@YAXXZ這個函數調用就沒有了著落,因為在鏈接器看來,B文件中沒有名為?func1@@YAXXZ的函數。
???????事實是,我們編程者知道,B文件中有A中調用的func函數的定義,只不過它是按照C方式編譯的,故它的名字被改成了_func。因而,我們需要通過某種方式告訴編譯器:“B中定義的函數func()經編譯后命名成了_func,而不是?func1@@YAXXZ,你必須通過call _func來調用它,而不是call ?func1@@YAXXZ。”簡單的說,就是告訴編譯器,調用的func()函數是以C方式編譯的,fun();語句必須被編譯成call _func;而不是call ?func1@@YAXXZ。
? ? ??我們可以通過extern關鍵字,來幫助編譯器解決上面提到的問題。
?對于本例,只需將A.CPP改成如下即可:
?
//A.CPP
?
extern "C"{?void func();}
?
void main(){?func();}
察看匯編代碼,發現此時的func();語句被編譯成了call _func。
三、C中調用C++代碼
在C中引用C++語言中的函數和變量時,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明了extern "C"的該頭文件,應該僅將C文件中將C++中定義的extern "C"函數聲明為extern類型。 筆者編寫的C引用C++函數例子工程中包含的三個文件的源代碼如下: //C++頭文件 cppExample.h #ifndef CPP_EXAMPLE_H #define CPP_EXAMPLE_H extern "C" int add( int x, int y ); #endif //C++實現文件 cppExample.cpp #include "cppExample.h" int add( int x, int y ) { return x + y; } /* C實現文件 cFile.c /* 這樣會編譯出錯:#include "cppExample.h" */ extern int add( int x, int y ); int main( int argc, char* argv[] ) { add( 2, 3 ); return 0; } 總結 C和C++對函數的處理方式是不同的.extern "C"是使C能夠調用C++寫的庫文件的一個手段,如果要對編譯器提示使用C的方式來處理函數的話,那么就要使用extern "C"來說明。?
?
總結
以上是生活随笔為你收集整理的extern “C”的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: XEN的漫漫人生路
- 下一篇: 关于runjs的一些想法