前言
在重构sample sdk代码,并编译成静态或动态库来提供给上层调用的API时,不可避免会遇到c++调用c或c调用c++的问题。看我博客的网友也提到了这个问题,所以藉这个机会来把C和C++混合编程(即相互调用)的问题透彻的弄清楚。
问题背景
C和C++直接相互调用 之所以会出问题的原因是, C++里面有函数重载的概念,从而导致编译出来的函数名会带上参数类型,而C编译出来的函数名简单的就是其本身。
下面定义了两个简单的c、c++头文件和实现文件
/*test_c.h*/
#ifndef TEST_C
#define TEST_C
int add(int a, int b);
#endif
/*test_c.c*/
#include "test_c.h"
int add(int a, int b)
{
int c;
c = a + b;
return c;
}
/*test_c++.h*/
#ifndef TEST_C_PLUSPLUS
#define TEST_C_PLUSPLUS
int substract(int a, int b);
#endif
/*test_c++.cpp*/
#include "test_c++.h"
int substract(int a, int b)
{
int c;
c = a - b;
return c;
}
分别使用两个命令生成obj文件。
gcc -c test_c.c
gcc -c test_c++.cpp
然后使用命令: nm test_c.o | grep add 和 nm test_c++.o | grep substract 得到如下结果:
add 和 _Z9substractii。 这个结果进一步验证了前面所说的,即C和C++编译出来的函数名会不一样。
C调用C++
添加一个C测试文件main.c,并调用add和substract API。
/*main.c*/
#include <stdio.h>
#include "test_c.h"
#include "test_c++.h"
int main(void)
{
int x = 3;
int y = 5;
printf("add = %d \n", add(x, y));
printf("sub = %d \n", substract(x, y));
return 0;
}
使用命令如下来将它们编译成一个可执行文件test_c:
gcc test_c.c test_c++.cpp main.c -o test_c
但这个时候会报链接错误: main.c:(.text+0x43): undefined reference to 'substract' 。明明我们在test_c++.cpp里面定义了substract(),但是main.c不识别。 这也说明了 c直接调用c++代码出现错误。
解决办法就是引入 extern "C"。 关于它有两个重要注意点:
1)extern "C"是c++语法,只能被c++编译器认识,其目的就是告诉C++编译器,其被extern "C"所包含的函数得以C方式(即简单函数名)去链接。
2) 为了避免C编译器来编译它来产生编译错误, 保险起见,会将 extern "C" {和}用#ifdef __cplusplus... #endif修饰起来。这里很容易犯得一个错误就是 cplusplus前面是两根下划线"_"。
回到上面得错误,我们得在test_c++.cpp编译时,得用c方式去来生成substract函数。所以test_cpp.h更新如下,并用__cplusplus保护起来,避免main.c编译时报错。
/*test_c++.h*/
#ifndef TEST_C_PLUSPLUS
#define TEST_C_PLUSPLUS
#ifdef __cplusplus
extern "C" {
#endif
int substract(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
再次编译链接: gcc test_c.c test_c++.cpp main.c -o test_c没有问题,并执行./test_c 结果也完全正确。
C++调用C
添加一个C++测试文件main.cpp来调用add和substract。
/*main.cpp*/
#include <stdio.h>
#include "test_c.h"
#include "test_c++.h"
int main(void)
{
int x = 3;
int y = 5;
printf("add = %d \n", add(x, y));
printf("sub = %d \n", substract(x, y));
return 0;
}
使用类似命令来编译链接: gcc test_c.c test_c++.cpp main.cpp -o test_cpp,结果也出现main.cpp里面找不到定义在test_c.c里面得add函数。
同样地,必须在test_c.h里面添加extern ”C“来告诉main.cpp编译链接时得以c方式来寻找add函数。同时使用__cplusplus修饰避免 test_c.c编译失败。
/*test_c.h*/
#ifndef TEST_C
#define TEST_C
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
使用编译链接命令:gcc test_c.c test_c++.cpp main.cpp -o test_cpp 没有问题,并执行./test_cpp 结果也完全正确。
结论
无论是C++调用C api 还是C调用C++ API, 必须先在API所对应得头文件对API进行 extern "C"声明,并用#ifdef __cplusplus进行保护。