extern“c”

extern“c”

extern "C" 是c++語言,包含雙重含義,從字面上即可得到:首先,被它修飾的目標是"extern"的;其次,被它修飾的目標是"C"的。讓我們來詳細解讀這兩重含義。

extern“c”

含義

(1) 被 extern限定的 函式或 變數是 extern類型的:

a. extern修飾 變數的聲明。 舉例來說,如果檔案a.c需要引用b.c中 變數int v,就可以在a.c中聲明 externint v,然後就可以 引用變數v。這裡需要注意的是,被引用的 變數v的連結屬性必須是外連結( external)的,也就是說a.c要引用到v,不只是取決於在a.c中聲明 externint v,還取決於 變數v本身是能夠被引用到的。這涉及到 c語言的另外一個話題-- 變數的 作用域。能夠被其他模組以extern 修飾符引用到的變數通常是 全局變數。還有很重要的一點是, externint v可以放在a.c中的任何地方,比如你可以在a.c中的 函式fun定義的開頭處聲明extern int v,然後就可以引用到 變數v了,只不過這樣只能在 函式fun 作用域中引用v罷了,這還是變數作用域的問題。對於這一點來說,很多人使用的時候都心存顧慮。好像 extern聲明只能用於檔案 作用域似的。

b. extern修飾 函式聲明。從本質上來講, 變數和函式沒有區別。 函式名是指向函式二進制塊開頭處的 指針。如果檔案a.c需要引用b.c中的 函式,比如在b.c中原型是int fun(int mu),那么就可以在a.c中聲明extern int fun(int mu),然後就能使用fun來做任何事情。就像 變數的聲明一樣, externint fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的檔案 作用域的範圍中。對其他模組中 函式的引用,最常用的方法是包含這些函式聲明的頭檔案。使用 extern和包含頭檔案來引用 函式有什麼區別呢?extern的引用方式比包含頭檔案要簡潔得多!extern的使用方法是直截了當的,想引用哪個函式就用extern聲明哪個函式。這大概是KISS原則的一種體現吧!這樣做的一個明顯的好處是,會加速程式的編譯(確切的說是預處理)的過程,節省時間。在大型C 程式編譯過程中,這種差異是非常明顯的。

(2) 被extern "C"修飾的變數和 函式是按照C語言方式編譯和連線的;

未加 extern“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++中的 變數除支持 局部變數外,還支持類 成員變數和 全局變數。用戶所編寫程式的類 成員變數可能與 全局變數同名,我們以"."來區分。而本質上, 編譯器在進行編譯時,與 函式的處理相似,也為類中的 變數取了一個獨一無二的名字,這個名字與 用戶程式中同名的 全局變數名字不同。

實例

未加 extern"C"聲明時的連線方式

假設在C++中,模組A的頭檔案如下:

// 模組A頭檔案 moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, int y );

#endif

在模組B中引用該 函式:

// 模組B實現檔案 moduleB.cpp

#i nclude "moduleA.h"

foo(2,3);

實際上,在連線階段,連線器會從模組A生成的目標檔案moduleA.obj中尋找_foo_int_int這樣的符號!

加extern "C"聲明後的編譯和連線方式

加extern "C"聲明後,模組A的頭檔案變為:

// 模組A頭檔案 moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

extern "C" int foo( int x, int y );

#endif

在模組B的實現檔案中仍然調用foo(2,3),其結果是:

(1)模組A編譯生成foo的 目標代碼時,沒有對其名字進行特殊處理,採用了C語言的方式;

(2)連線器在為模組B的 目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。

如果在模組A中 函式聲明了foo為extern "C"類型,而模組B中包含的是extern int foo( int x, int y ) ,則模組B找不到模組A中的函式;反之亦然。

所以,可以用一句話概括 extern“C”這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎么做的,還要問一問它為什麼要這么做,動機是什麼,這樣我們可以更深入地理解許多問題):

實現C++與C及其它語言的 混合編程。

明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧。

extern"C"的慣用法

(1)在C++中引用C語言中的 函式和 變數,在包含C語言頭檔案(假設為cExample.h)時,需進行下列處理:

extern "C"

{

#i nclude "cExample.h"

}

而在C語言的頭檔案中,對其外部 函式只能指定為 extern類型,C語言中不支持extern "C"聲明,在.c檔案中包含了extern "C"時會出現編譯語法錯誤。

筆者編寫的 C++引用C 函式例子工程中包含的三個檔案的 原始碼如下:

/* c語言頭檔案:cExample.h */

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

externint add(int x,int y);

#endif

/* c語言實現檔案:cExample.c */

#i nclude "cExample.h"

int add( int x, int y )

{

return x + y;

}

// c++實現檔案,調用add:cppFile.cpp

extern "C"

{

#i nclude "cExample.h"

}

int main(int argc, char* argv[])

{

add(2,3);

return 0;

}

(注意這裡如果用GCC編譯的時候,請先使用gcc -c選項生成cExample.o,再使用g++ -o cppFile cppFile.cpp cExample.o才能生成預期的 c++調用 c函式的結果,否則,使用g++ -o cppFile cppFile.cpp cExample.c 編譯器會報錯;而當cppFile.cpp 檔案中不使用下列語句

extern "C"

{

#i nclude "cExample.h"

}

而改用

#i nclude "cExample.h"

extern "C" int add( int x, int y );

g++ -o cppFile cppFile.cpp cExample.c的編譯過程會把add 函式按 c++的方式解釋為_foo_int_int這樣的符號。

如果C++調用一個C語言編寫的.DLL時,當包括.DLL的頭檔案或聲明 接口函式時,應加extern "C" { }。

(2)在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

#i nclude "cppExample.h"

int add( int x, int y )

{

return x + y;

}

/* C實現檔案 cFile.c

/* 這樣會編譯出錯:#i nclude "cppExample.h" */

extern int add( int x, int y );

int main( int argc, char* argv[] )

{

add( 2, 3 );

return 0;

}

相關詞條

相關搜尋

熱門詞條

聯絡我們