extern C的作用詳解
extern "C"的主要作用就是為了能夠正確實現C++程式碼呼叫其他C語言程式碼。加上extern "C"後,會指示編譯器這部分程式碼按C語言的進行編譯,而不是C++的。由於C++支援函式過載,因此編譯器編譯函式的過程中會將函式的引數型別也加到編譯後的程式碼中,而不僅僅是函式名;而C語言並不支援函式過載,因此編譯C語言程式碼的函式時不會帶上函式的引數型別,一般之包括函式名。
這個功能十分有用處,因為在C++出現以前,很多程式碼都是C語言寫的,而且很底層的庫也是C語言寫的,為了更好的支援原來的C程式碼和已經寫好的C語言庫,需要在C++中儘可能的支援C,而extern "C"就是其中的一個策略。
這個功能主要用在下面的情況:
1、C++程式碼呼叫C語言程式碼
2、在C++的標頭檔案中使用
3、在多個人協同開發時,可能有的人比較擅長C語言,而有的人擅長C++,這樣的情況下也會有用到
給出一個我設計的例子:
moduleA、moduleB兩個模組,B呼叫A中的程式碼,其中A是用C語言實現的,而B是利用C++實現的,下面給出一種實現方法:
//moduleA標頭檔案
#ifndef __MODULE_A_H //對於模組A來說,這個巨集是為了防止標頭檔案的重複引用
#define __MODULE_A_H
int fun(int, int);
#endif
//moduleA實現檔案moduleA.C //模組A的實現部分並沒有改變
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
//moduleB標頭檔案
#idndef __MODULE_B_H //很明顯這一部分也是為了防止重複引用
#define __MODULE_B_H
#ifdef __cplusplus //而這一部分就是告訴編譯器,如果定義了__cplusplus(即如果是cpp檔案, extern "C"{ //因為cpp檔案預設定義了該巨集),則採用C語言方式進行編譯
#include"moduleA.h"
#endif
… //其他程式碼
#ifdef __cplusplus
}
#endif
#endif
//moduleB實現檔案 moduleB.cpp //B模組的實現也沒有改變,只是標頭檔案的設計變化了
#include"moduleB.h"
int main()
{
cout<<fun(2,3)<<endl;
}
下面是詳細的介紹:
由於C、C++編譯器對函式的編譯處理是不完全相同的,尤其對於C++來說,支援函式的過載,編譯後的函式一般是以函式名和形參型別來命名的。
例如函式void fun(int, int),編譯後的可能是(不同編譯器結果不同)_fun_int_int(不同編譯器可能不同,但都採用了類似的機制,用函式名和引數型別來命名編譯後的函式名);而C語言沒有類似的過載機制,一般是利用函式名來指明編譯後的函式名的,對應上面的函式可能會是_fun這樣的名字。
看下面的一個面試題:為什麼標準標頭檔案都有類似的結構?
#ifndef __INCvxWorksh /*防止該標頭檔案被重複引用*/
#define __INCvxWorksh
#ifdef __cplusplus //告訴編譯器,這部分程式碼按C語言的格式進行編譯,而不是C++的
extern "C"{
#endif
/*…*/
#ifdef __cplusplus
}
#endif
#endif /*end of __INCvxWorksh*/
分析:
- 顯然,標頭檔案中編譯巨集"#ifndef __INCvxWorksh 、#define __INCvxWorksh、#endif"(即上面程式碼中的藍色部分)的作用是為了防止該標頭檔案被重複引用
- 那麼
#ifdef __cplusplus (其中__cplusplus是cpp中自定義的一個巨集!!!)
extern "C"{
#endif
#ifdef __cplusplus
}
#endif
的作用是什麼呢?
extern "C"包含雙重含義,從字面上可以知道,首先,被它修飾的目標是"extern"的;其次,被它修飾的目的碼是"C"的。
- 被extern "C"限定的函式或變數是extern型別的
extern是C/C++語言中表明函式和全域性變數的作用範圍的關鍵字,該關鍵字告訴編譯器,其申明的函式和變數可以在本模組或其他模組中使用。
記住,下面的語句:
extern int a; 僅僅是一個變數的宣告,其並不是在定義變數a,並未為a分配空間。變數a在所有模組中作為一種全域性變數只能被定義一次,否則會出錯。
通常來說,在模組的標頭檔案中對本模組提供給其他模組引用的函式和全域性變數以關鍵字extern生命。例如,如果模組B要引用模組A中定義的全域性變數和函式時只需包含模組A的標頭檔案即可。這樣模組B中呼叫模組A中的函式時,在編譯階段,模組B雖然找不到該函式,但並不會報錯;它會在連結階段從模組A編譯生成的目的碼中找到該函式。
extern對應的關鍵字是static,static表明變數或者函式只能在本模組中使用,因此,被static修飾的變數或者函式不可能被extern C修飾。
- 被extern "C"修飾的變數和函式是按照C語言方式進行編譯和連結的:這點很重要!!!!
上面也提到過,由於C++支援函式過載,而C語言不支援,因此函式被C++編譯後在符號庫中的名字是與C語言不同的;C++編譯後的函式需要加上引數的型別才能唯一標定過載後的函式,而加上extern "C"後,是為了向編譯器指明這段程式碼按照C語言的方式進行編譯
未加extern "C"宣告時的連結方式:
//模組A標頭檔案 moduleA.h
#idndef _MODULE_A_H
#define _MODULE_A_H
int foo(int x, int y);
#endif
在模組B中呼叫該函式:
//模組B實現檔案 moduleB.cpp
#include"moduleA.h"
foo(2,3);
實際上,在連結階段,聯結器會從模組A生成的目標檔案moduleA.obj中找_foo_int_int這樣的符號!!!,顯然這是不可能找到的,因為foo()函式被編譯成了_foo的符號,因此會出現連結錯誤。
常見的做法可以參考下面的一個實現:
moduleA、moduleB兩個模組,B呼叫A中的程式碼,其中A是用C語言實現的,而B是利用C++實現的,下面給出一種實現方法:
//moduleA標頭檔案
#ifndef __MODULE_A_H //對於模組A來說,這個巨集是為了防止標頭檔案的重複引用
#define __MODULE_A_H
int fun(int, int);
#endif
//moduleA實現檔案moduleA.C //模組A的實現部分並沒有改變
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
//moduleB標頭檔案
#idndef __MODULE_B_H //很明顯這一部分也是為了防止重複引用
#define __MODULE_B_H
#ifdef __cplusplus //而這一部分就是告訴編譯器,如果定義了__cplusplus(即如果是cpp檔案, extern "C"{ //因為cpp檔案預設定義了該巨集),則採用C語言方式進行編譯
#include"moduleA.h"
#endif
… //其他程式碼
#ifdef __cplusplus
}
#endif
#endif
//moduleB實現檔案 moduleB.cpp //B模組的實現也沒有改變,只是標頭檔案的設計變化了
#include"moduleB.h"
int main()
{
cout<<fun(2,3)<<endl;
}
extern "C"的使用要點
1. 可以是單一語句
extern "C" double sqrt(double);
2. 可以是複合語句, 相當於複合語句中的宣告都加了extern "C"
extern "C"
{
double sqrt(double);
int min(int, int);
}
3.可以包含標頭檔案,相當於標頭檔案中的宣告都加了extern "C"
extern "C"
{
#i nclude <cmath>
}
4. 不可以將extern "C" 新增在函式內部
5. 如果函式有多個宣告,可以都加extern "C", 也可以只出現在第一次宣告中,後面的宣告會接受第一個連結指示符的規則。
6. 除extern "C", 還有extern "FORTRAN" 等。
參考文章: