概述:比较详细的解释C/C++混合编译的原理,基于GNU工具演示。
基础知识:GNU工具链的简单使用,能明白 gcc -o -c这样的命令就行了,自己调试运行一次代码就能明白。如果只是照这个别人的代码,写了一个中间层去满足混合编译的需求,最后你还是会把自己绕晕。
看看这个或许就恍然大悟:GCC手册
先要把这两个错误的想法彻底弄明白,才能把混合编译弄清楚。
错误1:gcc只能编译c,g++只能编译c++。
错误2:使用g++编译链接__cplusplus才会定义。
编译器和源文件的关系:通过以下的分析可以推导出这个表的,彻底理解extern "C"的处理机制。
源文件 | 编译命令 | 编译结果 | __cplusplus是否定义 | 默认语法规则 | C++库链接 |
source.c | gcc | 成功 | 无 | C | |
source.cpp | gcc | 成功 | 有 | c++\函数重载 | 自动链接 |
source.c | g++ | 成功 | 有 | c++\函数重载 | 手动指定 |
source.cpp | g++ | 成功 | 有 | c++\函数重载 | 自动链接 |
造成C/C++混合编译失败的根本原因分析
- g++、gcc标准库链接
- C++函数重载机制反汇编分析
- c/c++相互调用处理机制
1、g++、gcc标准库链接
#ifdef __cplusplus
#include <iostream>
using namespace std;
int flag = 1;
#else
#include "stdio.h"
int flag = 0;
#endif
int main(void)
{
#ifdef __cplusplus
cout<<"c++ code"<<flag<<endl;
cout<<"__cplusplus:"<<__cplusplus<<endl;
#else
printf("C code %d \r\n",flag);
#endif
return 0;
}
1、gcc编译C语言:常规操作,符合思路。__cplusplus无定义
将上述测试代码命名为gcc_c.c文件,编译运行,我相信你能理解,不理解下面就不用看了。
2、gcc编译C++:__cplusplus有定义。感觉有点奇怪,有时候行,有时候又不行。
gcc不会自动链接C++的标准库文件,若source.cpp文件中调用了C++标准库,使用gcc source.cpp。这条命令编译会失败。但是手动链接c++标准库,使用gcc source.cpp -lstdc++ 这条命令就可以编译成功。将上述测试代码命名为gcc_cpp.cpp的C++源文件,gcc gcc_cpp.cpp -o gcc_cpp编译试一下,编译不了,链接不到标准库。
如果source.cpp文件中,没有使用任何库文件,都是原生的语法,那gcc source.cpp会根据C++规则编译链接(也就是不需要手动指定链接库)成功。
将gcc_cpp.cpp文件生成gcc_cpp.o目标文件,运行gcc -c gcc_cpp.cpp,是可以成功生成的,但是运行gcc gcc_cpp.o -o gcc_cpp命令,又会出现上图一样的错误。
再手动指定C++的标准库,将gcc_cpp.o文件生成gcc_cpp可执行程序,运行命令 gcc gcc_cpp.o -o gcc_cpp -lstdc++。
运行./gcc_cpp成功,如下图所示。
3、g++编译C源文件:__cplusplus有定义。
完全可行,C++本来就兼容C语言,但是不推荐这么使用,尤其不要在CPP源文件使用C语言特性。
测试一下,g++生产的目标文件,使用gcc工具去链接生产可执行程序。
测试结果:如下图,执行失败,不能链接C++标准库。手动指定C++标准库,就可以成功生成运行。
4、g++编译c++:__cplusplus有定义,常规操作。
其实也是先调用gcc编译成目标文件,然后使用g++链接。
2、C++函数重载机制反汇编分析
1、gcc编译c源文件:__cplusplus无定义。使用C语言特性编译,无重载机制。
2、gcc编译C++源文件:__cplusplus有定义。使用C++语言特性,函数重载。
3、g++编译c源文件:__cplusplus有定义。使用C++语言特性,函数重载。
4、g++编译C++源文件:__cplusplus有定义。使用C++语言特性,函数重载。
3、c/c++相互调用处理机制
1、C调用C++:要解决什么问题?C++函数重载,C语言调用时如何寻找到对应的被调用函数。找不到就链接失败,无法生成可执行程序。
解决方案:添加一个中间层,把重载函数封装成不同名函数,使用C语言规则编译链接中间层。
被调用的C++代码:func.cpp 、func.h
#include <iostream>
using namespace std;
//函数重载
int add(int a,int b)
{
cout<<"int add() is called!"<<endl;
return a+b;
}
float add(float a,float b)
{
cout<<"float add() is called!"<<endl;
return a+b;
}
#ifndef _FUNC_H
#define _FUNC_H
int add(int a,int b);
float add(float a,float b);
#endif
中间层:interface.cpp、interface.h
#include "interface.h"
#include "func.h"
int Interface_AddInt(int a,int b)
{
return add(a,b);
}
float Interface_AddFloat(float a,float b)
{
return add(a,b);
}
#ifndef _INTERFACE_H
#define _INTERFACE_H
#ifdef __cplusplus
extern "C"
{
#endif
int Interface_AddInt(int a,int b);
float Interface_AddFloat(float a,float b);
#ifdef __cplusplus
}
#endif
#endif
main.c调用
#include "stdio.h"
#include "interface.h"
int main(void)
{
printf("1+1 : %d \r\n",Interface_AddInt(1,1) );
printf("1.1+1.1 :%f\r\n",Interface_AddFloat(1.1,1.1));
return 0;
}
编译运行:gcc main.c interface.cpp func.cpp -lstdc++
思考:中间层本质上是如何起作用的?
将 extern “C”注释后,如下图所示,main.c调用中间层时,找不到函数了。是因为根据C++的默认编译规则,中间层的函数名也被重载处理了,函数名不一样了。使用extern C 强制interface按照C语言规则进行编译链接,就可以成功生成可执行程序。
#ifndef _INTERFACE_H
#define _INTERFACE_H
// #ifdef __cplusplus
// extern "C"
// {
// #endif
int Interface_AddInt(int a,int b);
float Interface_AddFloat(float a,float b);
// #ifdef __cplusplus
// }
// #endif
#endif
2、C++调用C
mian.cpp
#include <iostream>
#include "myc.h"
using namespace std;
int main(void)
{
cout<<"add:"<< add(1,1)<<endl;
cout<<"__cplusplus:"<<__cplusplus<<endl;
return 0;
}
myc.c
int add(int a, int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
myc.h
#ifndef _MYC_H_
#define _MYC_H_
int add(int a, int b);
int sub(int a,int b);
#endif
情况1:g++编译链接main.cpp和myc.c,可成功生成可执行程序。但是myc.c使用C++规则编译,如下图。
情况2:gcc编译myc.c生成myc.o,g++编译main.cpp生成main.o,再用gcc/g++链接成可执行程序.
gcc编译myc.c使用C语言规则,gcc默认链接 gcc main.o myc.o会失败,因为gcc不会自动去链接C++的库。
但是手动指定gcc main.o myc.o -lstdc++,依然失败,因为链接不到add这个函数的地址。
解决方案:使用extern "C"{ },指示这一部分是C代码,要以兼容C的方式去处理,不能当成C++的代码。
#ifndef _MYC_H_
#define _MYC_H_
#ifdef __cplusplus
extern "C"
{
#endif
int add(int a, int b);
int sub(int a,int b);
#ifdef __cplusplus
}
#endif
#endif
如下图,使用gcc和g++来链接生成可执行文件都可以,只是使用gcc不会自动链接c++标准库,要手动指定。
gcc和g++都行?是不是晕?
小结:在一般的项目开发中,使用make组织代码,都是把C源文件使用xx_gcc(交叉编译工具)根据C语言规格汇编成目标文件c_source.o,把cpp源文件汇编成cpp_source.o,再使用xx_ld链接成可执行程序。所以,一个项目是C/C++混合编译,那被C++所用调用的C头文件,都要加上extern “c"来兼容。