在用C++的項目源碼中,常常會不可避免的會看到下面的代碼:ios
1
2
3
4
5
6
7
8
9
|
#ifdef __cplusplus
extern
"C"
{
#endif
/*...*/
#ifdef __cplusplus
}
#endif
|
它到底有什麼用呢,你知道嗎?並且這樣的問題常常會出如今面試or筆試中。下面我就從如下幾個方面來介紹它:程序員
在介紹extern "C"以前,咱們來看下#ifdef _cplusplus/#endif _cplusplus的做用。很明顯#ifdef/#endif、#ifndef/#endif用於條件編譯,#ifdef _cplusplus/#endif _cplusplus——表示若是定義了宏_cplusplus,就執行#ifdef/#endif之間的語句,不然就不執行。web
在這裏爲何須要#ifdef _cplusplus/#endif _cplusplus呢?由於C語言中不支持extern "C"聲明,若是你明白extern "C"的做用就知道在C中也沒有必要這樣作,這就是條件編譯的做用!在.c文件中包含了extern "C"時會出現編譯時錯誤。面試
既然說到了條件編譯,我就介紹它的一個重要應用——避免重複包含頭文件。還記得騰訊筆試就考過這個題目,給出相似下面的代碼(下面是我最近在研究的一個開源web服務器——Mongoose的頭文件mongoose.h中的一段代碼):編程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#ifdef __cplusplus
extern
"C"
{
#endif /* __cplusplus */
/*.................................
* do something here
*.................................
*/
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* MONGOOSE_HEADER_INCLUDED */
|
而後叫你說明上面宏#ifndef/#endif的做用?爲了解釋一個問題,咱們先來看兩個事實:服務器
爲了解決這個問題,上面代碼中的mongoose
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
/*……………………………*/
#endif /* MONGOOSE_HEADER_INCLUDED */函數
就起做用了。若是定義了MONGOOSE_HEADER_INCLUDED,#ifndef/#endif之間的內容就被忽略掉。所以,編譯時第一次看到mongoose.h頭文件,它的內容會被讀取且給定MONGOOSE_HEADER_INCLUDED一個值。以後再次看到mongoose.h頭文件時,MONGOOSE_HEADER_INCLUDED就已經定義了,mongoose.h的內容就不會再次被讀取了。佈局
首先從字面上分析extern "C",它由兩部分組成——extern關鍵字、"C"。下面我就從這兩個方面來解讀extern "C"的含義。spa
在一個項目中必須保證函數、變量、枚舉等在全部的源文件中保持一致,除非你指定定義爲局部的。首先來一個例子:
1
2
3
4
5
6
7
|
//file1.c:
int
x=1;
int
f(){
do
something here}
//file2.c:
extern
int
x;
int
f();
void
g(){x=f();}
|
在file2.c中g()使用的x和f()是定義在file1.c中的。extern關鍵字代表file2.c中x,僅僅是一個變量的聲明,其並非在定義變量x,並未爲x分配內存空間。變量x在全部模塊中做爲一種全局變量只能被定義一次,不然會出現鏈接錯誤。可是能夠聲明屢次,且聲明必須保證類型一致,如:
1
2
3
4
5
6
7
8
9
|
//file1.c:
int
x=1;
int
b=1;
extern
c;
//file2.c:
int
x;
// x equals to default of int type 0
int
f();
extern
double
b;
extern
int
c;
|
在這段代碼中存在着這樣的三個錯誤:
回到extern關鍵字,extern是C/C++語言中代表函數和全局變量做用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量能夠在本模塊或其它模塊中使用。一般,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,若是模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件便可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,可是並不會報錯;它會在鏈接階段中從模塊A編譯生成的目標代碼中找到此函數。
與extern對應的關鍵字是 static,被它修飾的全局變量和函數只能在本模塊中使用。所以,一個函數或變量只可能被本模塊使用時,其不可能被extern 「C」修飾。
典型的,一個C++程序包含其它語言編寫的部分代碼。相似的,C++編寫的代碼片斷可能被使用在其它語言編寫的代碼中。不一樣語言編寫的代碼互相調用是困難的,甚至是同一種編寫的代碼但不一樣的編譯器編譯的代碼。例如,不一樣語言和同種語言的不一樣實現可能會在註冊變量保持參數和參數在棧上的佈局,這個方面不同。
爲了使它們遵照統一規則,可使用extern指定一個編譯和鏈接規約。例如,聲明C和C++標準庫函數strcyp(),並指定它應該根據C的編譯和鏈接規約來連接:
1
|
extern
"C"
char
*
strcpy
(
char
*,
const
char
*);
|
注意它與下面的聲明的不一樣之處:
1
|
extern
char
*
strcpy
(
char
*,
const
char
*);
|
下面的這個聲明僅表示在鏈接的時候調用strcpy()。
extern "C"指令很是有用,由於C和C++的近親關係。注意:extern "C"指令中的C,表示的一種編譯和鏈接規約,而不是一種語言。C表示符合C語言的編譯和鏈接規約的任何語言,如Fortran、assembler等。
還有要說明的是,extern "C"指令僅指定編譯和鏈接規約,但不影響語義。例如在函數聲明中,指定了extern "C",仍然要遵照C++的類型檢測、參數轉換規則。
再看下面的一個例子,爲了聲明一個變量而不是定義一個變量,你必須在聲明時指定extern關鍵字,可是當你又加上了"C",它不會改變語義,可是會改變它的編譯和鏈接方式。
若是你有不少語言要加上extern "C",你能夠將它們放到extern "C"{ }中。
經過上面兩節的分析,咱們知道extern "C"的真實目的是實現類C和C++的混合編程。在C++源文件中的語句前面加上extern "C",代表它按照類C的編譯和鏈接規約來編譯和鏈接,而不是C++的編譯的鏈接規約。這樣在類C的代碼中就能夠調用C++的函數or變量等。(注:我在這裏所說的類C,表明的是跟C語言的編譯和鏈接方式一致的全部語言)
咱們既然知道extern "C"是實現的類C和C++的混合編程。下面咱們就分別介紹如何在C++中調用C的代碼、C中調用C++的代碼。首先要明白C和C++互相調用,你得知道它們之間的編譯和鏈接差別,及如何利用extern "C"來實現相互調用。
C++是一個面嚮對象語言(雖不是純粹的面嚮對象語言),它支持函數的重載,重載這個特性給咱們帶來了很大的便利。爲了支持函數重載的這個特性,C++編譯器實際上將下面這些重載函數:
1
2
3
4
|
void
print(
int
i);
void
print(
char
c);
void
print(
float
f);
void
print(
char
* s);
|
編譯爲:
1
2
3
4
|
_print_int
_print_char
_print_float
_pirnt_string
|
這樣的函數名,來惟一標識每一個函數。注:不一樣的編譯器實現可能不同,可是都是利用這種機制。因此當鏈接是調用print(3)時,它會去查找_print_int(3)這樣的函數。下面說個題外話,正是由於這點,重載被認爲不是多態,多態是運行時動態綁定(「一種接口多種實現」),若是硬要認爲重載是多態,它頂可能是編譯時「多態」。
C++中的變量,編譯也相似,如全局變量可能編譯g_xx,類變量編譯爲c_xx等。鏈接是也是按照這種機制去查找相應的變量。
C語言中並無重載和類這些特性,故並不像C++那樣print(int i),會被編譯爲_print_int,而是直接編譯爲_print等。所以若是直接在C++中調用C的函數會失敗,由於鏈接是調用C中的print(3)時,它會去找_print_int(3)。所以extern "C"的做用就體現出來了。
假設一個C的頭文件cHeader.h中包含一個函數print(int i),爲了在C++中可以調用它,必需要加上extern關鍵字(緣由在extern關鍵字那節已經介紹)。它的代碼以下:
1
2
3
4
5
6
|
#ifndef C_HEADER
#define C_HEADER
extern
void
print(
int
i);
#endif C_HEADER
|
相對應的實現文件爲cHeader.c的代碼爲:
1
2
3
4
5
6
|
#include <stdio.h>
#include "cHeader.h"
void
print(
int
i)
{
printf
(
"cHeader %d\n"
,i);
}
|
如今C++的代碼文件C++.cpp中引用C中的print(int i)函數:
1
2
3
4
5
6
7
8
9
|
extern
"C"
{
#include "cHeader.h"
}
int
main(
int
argc,
char
** argv)
{
print(3);
return
0;
}
|
執行程序輸出:
如今換成在C中調用C++的代碼,這與在C++中調用C的代碼有所不一樣。以下在cppHeader.h頭文件中定義了下面的代碼:
1
2
3
4
5
6
|
#ifndef CPP_HEADER
#define CPP_HEADER
extern
"C"
void
print(
int
i);
#endif CPP_HEADER
|
相應的實現文件cppHeader.cpp文件中代碼以下:
1
2
3
4
5
6
7
8
|
#include "cppHeader.h"
#include <iostream>
using
namespace
std;
void
print(
int
i)
{
cout<<
"cppHeader "
<<i<<endl;
}
|
在C的代碼文件c.c中調用print函數:
1
2
3
4
5
6
|
extern
void
print(
int
i);
int
main(
int
argc,
char
** argv)
{
print(3);
return
0;
}
|
注意在C的代碼文件中直接#include "cppHeader.h"頭文件,編譯出錯。並且若是不加extern int print(int i)編譯也會出錯。
當咱們C和C++混合編程時,有時候會用一種語言定義函數指針,而在應用中將函數指針指向另外一中語言定義的函數。若是C和C++共享同一中編譯和鏈接、函數調用機制,這樣作是能夠的。然而,這樣的通用機制,一般否則假定它存在,所以咱們必須當心地確保函數以指望的方式調用。
並且當指定一個函數指針的編譯和鏈接方式時,函數的全部類型,包括函數名、函數引入的變量也按照指定的方式編譯和鏈接。以下例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
typedef
int
(*FT) (
const
void
* ,
const
void
*);
//style of C++
extern
"C"
{
typedef
int
(*CFT) (
const
void
*,
const
void
*);
//style of C
void
qsort
(
void
* p,
size_t
n,
size_t
sz,CFT cmp);
//style of C
}
void
isort(
void
* p,
size_t
n,
size_t
sz,FT cmp);
//style of C++
void
xsort(
void
* p,
size_t
n,
size_t
sz,CFT cmp);
//style of C
//style of C
extern
"C"
void
ysort(
void
* p,
size_t
n,
size_t
sz,FT cmp);
int
compare(
const
void
*,
const
void
*);
//style of C++
extern
"C"
ccomp(
const
void
*,
const
void
*);
//style of C
void
f(
char
* v,
int
sz)
{
//error,as qsort is style of C
//but compare is style of C++
qsort
(v,sz,1,&compare);
qsort
(v,sz,1,&ccomp);
//ok
isort(v,sz,1,&compare);
//ok
//error,as isort is style of C++
//but ccomp is style of C
isort(v,sz,1,&ccopm);
}
|
注意:typedef int (*FT) (const void* ,const void*),表示定義了一個函數指針的別名FT,這種函數指針指向的函數有這樣的特徵:返回值爲int型、有兩個參數,參數類型能夠爲任意類型的指針(由於爲void*)。
最典型的函數指針的別名的例子是,信號處理函數signal,它的定義以下:
1
2
|
typedef
void
(*HANDLER)(
int
);
HANDLER
signal
(
int
,HANDLER);
|
上面的代碼定義了信函處理函數signal,它的返回值類型爲HANDLER,有兩個參數分別爲int、HANDLER。 這樣避免了要這樣定義signal函數:
1
|
void
(*
signal
(
int
,
void
(*)(
int
) ))(
int
)
|
比較以後能夠明顯的體會到typedef的好處。
感謝原文做者分享:
做者:吳秦
出處:http://www.cnblogs.com/skynet/
本文基於署名 2.5 中國大陸許可協議發佈,歡迎轉載,演繹或用於商業目的,可是必須保留本文的署名吳秦(包含連接).