#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
在C++项目源码中,经常会有上述代码,接下来我们将要介绍一下,它们到底有什么用。
1、#ifdef _cplusplus 和#endif _cplusplus
我们知道 #ifdef/#endif、#ifndef/#endif
用于条件编译,#ifdef _cplusplus/#endif _cplusplus
则表示如果定义了宏 _cplusplus
,就执行#ifdef/#endif
之间的语句,否则就不执行。条件编译有一个重要的作用就是避免重复包含头文件。
举个例子:
#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 */
这个头文件mongoose.h
可能在项目中被多个源文件包含(#include “mongoose.h”
),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中mongoose.h可能会被#include两次(如,a.h头文件包含mongoose.h,而在b.c文件中#include a.h和mongoose.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
为了解决这个问题,上面代码中的
#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的内容就不会再次被读取了。
2、extern “C”
2.1、关键字 extern
extern
是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。
例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与extern
对应的关键字是 static
,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
2.2、‘C’
一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。但是不同语言编写的代码相互之间调用是很难的。因为不同语言在保持参数和参数在栈上的布局可能是不同的,所以为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。
extern "C"
指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。还有要说明的是,extern “C”
指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern “C”,仍然要遵守C++的类型检测、参数转换规则。
extern “C”
的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”
,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。
3、C++调用C的代码
我们既然知道extern “C”
是实现的类C和C++的混合编程。下面我们就分别介绍如何在C++中调用C的代码、C中调用C++的代码。首先要明白C和C++互相调用,你得知道它们之间的编译和连接差异,及如何利用extern “C”
来实现相互调用。
3.1、C++的编译和连接
C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:
void print(int i);
void print(char c);
void print(float f);
void print(char* s);
编译为:
_print_int
_print_char
_print_float
_pirnt_string
注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找_print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。
3.2、C 的编译和连接
C语言中并没有重载和类这些特性,故并不像 C++ 那样print(int i),会被编译为 print_int,而是直接编译为 print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的print(3)时,它会去找_print_int(3)。因此extern “C”
的作用就体现出来了。
3.3、C++中调用C的代码
假设一个C 的头文件cHeader.h
中包含一个函数print(int i),为了在 C++ 中能够调用它,必须要加上extern
关键字(原因在extern关键字那节已经介绍)。
#ifndef C_HEADER
#define C_HEADER
extern void print(int i);
#endif C_HEADER
相对应的实现文件为cHeader.c的代码为:
#include <stdio.h>
#include "cHeader.h"
void print(int i)
{
printf("cHeader %d\n",i);
}
现在C++的代码文件C++.cpp中引用C中的print(int i)函数:
extern "C"{
#include "cHeader.h"
}
int main(int argc,char** argv)
{
print(3);
return 0;
}