这两天在做将用C编写的FFmpeg移植到自己的C++项目中,总是报错:error C2732: 链接规范与“av_malloc”的早期规范冲突。查找了下,是因为在C++程序中调用C文件里写的函数时需要加上 extern ”C"。 此处整理一下C++和C函数的相互调用方法,主要参考自http://www.cnblogs.com/dpflnevergiveup/p/3288966.html 和 http://www.cnblogs.com/dpflnevergiveup/p/3289024.html,只是在第二篇文章中自己调试改错后加上了自己的解决方案。
一、 extern "C" 与 C++中的C函数调用(上)(参考自http://www.cnblogs.com/dpflnevergiveup/p/3288966.html)
前段时间有人给我发了一篇如何在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 __cplusplus 10 } 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 <cmath> }
(4)不可以将extern "C" 添加在函数内部
(5)如果函数有多个声明,可以都加extern "C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
(6)除extern "C", 还有extern "FORTRAN" 等。
3、参考文献:
[1] 《编写高质量代码:改善C++程序的150个建议》,建议19:明白在C++中如何使用C
[2] 《(转)C++中extern “C”含义深层探索》http://www.cppblog.com/Macaulish/archive/2008/06/17/53689.html
[3] 《c++知识点--extern "C"的作用》http://blog.csdn.net/vinep/article/details/3899780
二、extern "C” 与 C++中的C函数调用(下) (参考自http://www.cnblogs.com/dpflnevergiveup/p/3289024.html, 有改动)
前面已经深入了解过extern "C"了,下面进一步探讨一下extern “C”的使用方法。
1、 C代码中包含extern “C”,C代码无法通过编译([1]中C++中调用C的方法1错误)
代码如下:
//C代码头文件CDemo.h #include <stdio.h> #ifndef C_SRC_DEMO_H #define C_SRC_DEMO_H extern "C" int f(int x,int y); #endif // C_SRC_DEMO_H
//C代码CDemo.c #include "CDemo.h" int f(int x,int y) { printf("In C file\n"); printf("x + y = %d\n",x+y); return 0; }
在Linux下,$:gcc –c CDemo.c 编译通不过
出错信息如下:
结论:在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。注:可以这样理解:extern "C"是告诉编译器,它后面声明的函数或变量时C的,因为本身写这个程序就是用C写的,没必要再画蛇添足写上“C”这个。
2、 如果在C头文件中函数声明了函数(f(int x,int y))为extern类型,而在C++中包含该头文件后,再重新声明并添加上extern "C"(extern int f( int x, int y )),C++文件编译通不过([1]中C++中调用C的方法2错误)
代码如下:
//C代码头文件CDemo.h #include <stdio.h> #ifndef C_SRC_DEMO_H #define C_SRC_DEMO_H extern int f(int x,int y); #endif // C_SRC_DEMO_H
//C代码 CDemo.c #include "CDemo.h" int f(int x,int y) { printf("In C file\n"); printf("x + y = %d\n",x+y); return 0; }
//C++代码 cppDemo.cpp #include "CDemo.h" #include <iostream> extern "C" int f(int x,int y); int main() { f(2,3); return 0; }
在Linux下,$:gcc -c CDemo.c 可以通过,并生成CDemo.o文件
$:g++ -c cppDemo.cpp 编译出错
出错信息:
注:我用原作者的此程序在VS2010下做了测试,报错为:error C2732: 链接规范与“f”的早期规范冲突。解决方案就是将cppDemo.cpp里面的extern "C" int f(int x, int y); 改成 extern “C" { #include CDemo.h }; 这样就解决了此错误。
但是还要注意一下两点:
(1)如果不重新声明,即C++代码更改为如下:
//C++代码 cppDemo.cpp #include "CDemo.h" #include <iostream> int main() { f(2,3); return 0; }
编译可以通过,但是将两个.o文件链接起来生成可执行文件时,会出错执行情况如下:
$g++ cppDemo.o CDemo.o –o cppDemo
这说明C++文件没有找到定义在C文件中的f函数,这是因为该函数被C编译器编译后在符号库中的名字与C++编译器产生的名字不同,如, C编译器产生的函数f的符号库中的名字为:f,而C++编译器产生的为_Z1fii(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
参考文献:
[1] 《编写高质量代码:改善C++程序的150个建议》,建议19:明白在C++中如何使用C
三、 C中如何调用C++函数 (参考自 http://www.cppblog.com/franksunny/archive/2007/11/29/37510.html)
将 C++ 函数声明为``extern "C"''(在你的 C++ 代码里做这个声明),然后调用它(在你的 C 或者 C++ 代码里调用)。例如:
// C++ code:
extern "C" void f(int);
void f(int i)
{
// ...
}
然后,你可以这样使用 f():
/* C code: */
void f(int);
void cc(int i)
{
f(i);
/* ... */
}
当然,这招只适用于非成员函数。如果你想要在 C 里调用成员函数(包括虚函数),则需要提供一个简单的包装(wrapper)。例如:
// C++ code:
class C
{
// ...
virtual double f(int);
};
extern "C" double call_C_f(C* p, int i) // wrapper function
{
return p->f(i);
}
然后,你就可以这样调用 C::f():
/* C code: */
double call_C_f(struct C* p, int i);
void ccc(struct C* p, int i)
{
double d = call_C_f(p,i);
/* ... */
}
如果你想在 C 里调用重载函数,则必须提供不同名字的包装,这样才能被 C 代码调用。例如:
// C++ code:
void f(int);
void f(double);
extern "C" void f_i(int i) { f(i); }
extern "C" void f_d(double d) { f(d); }
然后,你可以这样使用每个重载的 f():
/* C code: */
void f_i(int);
void f_d(double);
void cccc(int i,double d)
{
f_i(i);
f_d(d);
/* ... */
}
注意,这些技巧也适用于在 C 里调用 C++ 类库,即使你不能(或者不想)修改 C++ 头文件。
该翻译的文档Bjarne Stroustrup的原文链接地址是
http://www.research.att.com/~bs/bs_faq2.html#callCpp