前段时间有人给我发了一篇如何在C中调用C++函数的文章链接,我当时就想,我连如何在C++中调用C都不明白,还谈什么C中调用C++。不过我还是初略的看了一遍这篇文章,并从中了解到一个很有用的关键字:extern "C";后来我又查找如何在C++中调用C函数,里面也用到了extern “C”,所以我想要弄明白C和C++的相互调用,那就应该首先弄明白extern “C”。所以我到看了些博文,然后在前人的指引下,进行了一些实验,把实验结果和我的理解记录如下。
大多数跟这个有关的博文都有类似如下的一段话,这段话对了解C++有一个很好的前导作用,故而依葫芦画瓢抄录下来:
C++语言之父当初设计该语言的初衷是“a better C”,所以C++一般被认为是C的超集合,但是不要因此而误以为,“这意味着C++兼容C语言的所有东西”。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),大部分的C代码可以很轻易地在C++中正确编译,但仍有少数差异,导致某些有效的C代码在C++中无法通过编译。[1]
1、深入探索extern "C"
首先,分析下面的代码片段:
1 // Demo.h 2 #ifndef SRC_DEMO_H 3 #define SRC_DEMO_H 4 5 #ifdef __cplusplus 6 extern "C" { 7 #endif 8 /*...*/ 9 #ifdef __cplusplus10 }11 #endif 12 13 #endif // SRC_DEMO_H
显然,头文件中的编译宏“#ifndef SRC_DEMO_H、#define SRC_DEMO_H、#endif”的作用是防止该头文件被重复引用。那么,extern "C"又有什么特殊的作用呢?
下面先深入探索下extern "C"[2]:
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标“C”的。让我们来详细解读这两重含义。
(1)被extern "C"限定的函数或变量是extern类型的;
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:
extern int a;
仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
(2)被extern "C"修饰的变量和函数表示其是按照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++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
如果在C++中声明C中定义的函数时,未加extern "C"声明,那么在连接阶段,连接器会从.C文件生成的目标文件中寻找_foo_int_int这样的符号!
如果加上了extern "C"声明,那么连接器在为C++代码寻找f(2,3)的调用时,寻找的是是未经修改的符号名_foo。
所以,可以用一句话概括extern “C”这个声明的真实目的:实现C++与C及其它语言的混合编程。
2、extern "C"的使用要点[3]
(1)可以是单一语句
extern "C" double sqrt(double);
(2)可以是复合语句, 相当于复合语句中的声明都加了extern "C"
extern "C" { double sqrt(double); int min(int, int); }
(3)可以包含头文件,相当于头文件中的声明都加了extern "C"
extern "C" { #include}
(4)不可以将extern "C" 添加在函数内部
(5)如果函数有多个声明,可以都加extern "C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
(6)除extern "C", 还有extern "FORTRAN" 等。
3、参考文献:
[1] 《编写高质量代码:改善C++程序的150个建议》,建议19:明白在C++中如何使用C
[2] 《(转)C++中extern “C”含义深层探索》
[3] 《c++知识点--extern "C"的作用》