在 C++中调用 C 代码时,需要给编译器指定C代码要按照C语言的编译器编译,否则编译器会将C代码按照默认的C++编译器来编译C代码,这样在调用C代码时,会发生链接错误,找不到函数定义,因为C++编译器和C编译器对函数编译的过程都一点点区别。
下面先讲一下为什么会发送找不到函数定义的错误,进而弄清楚为什么需要extern “C” 来指定C编译器,然后再讲一个简单的例子说明如何使用 extern “C”。
C++编译器和C编译器
先来看一个简单的例子:
#include <iostream>
using namespace std;
void a(int i){
cout<<"a1: "<<i<<endl;
}
void a(int i, int j){
cout<<"a2: "<<i<<" and "<<j<<endl;
}
int main()
{
a(1);
a(2, 2);
return 0;
}
使用 g++ -c -o mian.o main.cpp 编译,然后readelf -s main.o:
13: 0000000000000000 58 FUNC GLOBAL DEFAULT 1 _Z1ai
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNS olsEi
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt 4endlIcSt11char_trait
18: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNS olsEPFRSoS_E
19: 000000000000003a 90 FUNC GLOBAL DEFAULT 1 _Z1aii
20: 0000000000000094 36 FUNC GLOBAL DEFAULT 1 main
21: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
22: 0000000000000000 0 NOTYPE GLOBAL HIDDEN UND __dso_handle
23: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
24: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit
主要看第13行和第19行的FUNC,C++中支持重载,实现的机制就是编译器对C++函数名进行一个处理,加上函数的参数类型,这样就可以唯一确定每一个C++函数了。比如这里C++编译器把 void a(int i) 函数名处理为 _Z1ai,把 void a(int i,int j) 函数名处理为 _Z1aii 。链接的时候通过 _Z1ai 和 _Z1aii 就可以找到对应的函数了。
然后我们再来看一个C例子:
#include <stdio.h>
void a(int i){
printf("a1: %d\n", i);
}
int main()
{
a(1);
return 0;
}
C语言不支持重载,所以只写了一个函数。同样,用gcc -c -o main.o main.c 编译,用readelf -s main.o查看:
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 a
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
11: 0000000000000022 21 FUNC GLOBAL DEFAULT 1 main
可以看到第9行的FUNC的名字 a 跟代码中的函数名是一样的。C代码中,就是通过这个名字来寻找对应的函数实现。
好了,现在我们知道了C++ 和C编译器对函数名的处理方式不同了。正是由于这种区别,我们要通过使用extern “C”来告诉编译器这是C代码,要用C编译器。
C++头文件中使用extern “C”
需要注意的是,extern “C”是C++语法,所以只能在C++文件中使用。在C++代码中使用C函数,可以在C++头文件中对C函数的头文件使用 extern “C”声明。最常用的格式如下:
//C++ headfile
#ifdef __cplusplus
extern "C" {
#endif
//一段C代码
#ifdef __cplusplus
}
#endif
下面举一个简单的例子来说明如何使用 extern “C”
4个文件,cprint.c,cprint.h 和 ccprint.cc ccprint.h。其中main函数在ccprint.cc中,并且调用cprint.c中的函数。
//cprint.h
#ifndef _CFUNCTION_H
#define _CFUNCTION_H
void CPrint( int num );
#endif
//cprint.c
#include "cfunction.h"
#include <stdio.h>
void CPrint( int num ) {
printf( "C function: %d.\n", num );
}
//ccprint.h
#ifndef _CPPFUNCTION_H
#define _CPPFUNCTION_H
void CppPrint( int num );
#ifdef __cplusplus
extern "C"{
#endif
#include "cfunction.h"
#ifdef __cplusplus
}
#endif
#endif
//ccprint.cc
#include "ccfunction.h"
#include <iostream>
void CppPrint( int num ) {
std::cout << "Cpp Print: " << num << std::endl;
}
int main() {
CPrint( 20 );
CppPrint( 10 );
return 0;
}
这样就可以成功编译和运行啦