extern "C"---编译语言连接字符串
部分摘自: http://blog.youkuaiyun.com/liruda/archive/2008/04/03/2247766.aspx
C++语言的创建初衷是“a better C”,不同于C语言的全局变量和函数所采用的编译和连接方式.作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数.但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同.
1.从标准头文件说起
某企业曾经给出如下的一道面试题:
面试题
为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
分析
显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用.
2.深层揭密extern "C" 的作用:
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的
(1) extern类型的:
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用.记住,下列语句:
extern int a;
仅仅是一个变量的声明 ,而非定义。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明.
与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰.
(2)按照C语言方式编译和连接的;
例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
未加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
#include "moduleA.h"
foo(2,3);
实际上, 未加extern "C",C++ 在连接阶段, 连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!
加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) 采用了C语言的方式,模块A编译生成foo的目标代码时
(2) 连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo.
一句话概括extern “C”这个声明的真实目的:
实现C++与C及其它语言的混合编程
3.extern "C"的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
//c++的头文件
extern "C"
{
#include "cExample.h"
}
而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误.
笔者编写的C++引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言的头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y); //不加extern也可以
#endif
/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++的文件,调用*.cpp
extern "C" //因为调用的是C语言
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }.
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型.
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
#ifdef __cplusplus
extern "C" {
#endif
int add(int x,int y);
#ifdef __cplusplus
}
#endif
#endif
//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
#include “”
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
重新编译并且链接就可以过去了.
总结
C++实现某个函数定义,C 调用它 :
//cpp.h
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
#ifdef __cplusplus
#define P 1
extern "C"
{
#else
#define P 0
#endif
int add(int x,int y); //感觉不需要extern
#ifdef __cplusplus
}
#endif
#endif //end C_EXAMPLE_H
// *.cpp
#include "cpp.h"
int add(int x,int y)
{
return x + y + P;
}
/* c语言头文件:main.h */
#include "cpp.h"
// main.c
#include "main.h"
int main(int argc, char* argv[])
{
printf("2+3 = %d", add(2,3));
printf("p= %d", P);
return 0;
}
结果为6,0 从结果可以知道,*.cpp文件会自行启用c++编译器。本程序先是根据main.c确定为c编译器,后遇到*.cpp需要用extern "C" 进行强制转换默认的c++编译器为C编译器。
而且,C文件使用P为0, CPP文件访问使用为1。也就是,看你调用此函数的调用文件为何类型。