实际在编译的过程中,.cpp 文件调用 .c文件中的函数会出错。假设代码结构如下:
目录
一、编译过程分析
1、预处理
2、编译
3、汇编
4、链接
二、问题解决
1、解决方案
2、解决思路
一、编译过程分析
1、预处理
该阶段头文件会被展开,此时只剩下 Add.c 和 main.cpp 文件。
2、编译
该阶段除了做一些语法、词法、语义分析、转换成汇编代码外,还会进行符号汇总
3、汇编
该阶段的目的是将汇编代码转换成二进制机器码,除此之外,还会生成符号表。符号名的修饰规则依赖于使用的编译器。.cpp 文件默认使用的是 c++编译器, 而 .c 文件默认使用的是 c 编译器
- C 编译器的命名规则:直接将函数名作为符号名,不做任何修饰
- C++编译器的命名规则:__Z + 返回值类型 + 函数名 + 形参类型
4、链接
在这个阶段,我们目前只关注一个功能,那就是将不同来自不同源文件的符号表汇总。
汇总以后发现,main.cpp 中用到了 Add 函数,因为在 .cpp 文件中,那么经过C++编译器生成的符号名就是__ZiAddii ,结果发现找不到对应的函数地址,这个时候就会报错。
VS环境下的报错是:
Linux环境下的报错是:
二、问题解决
下面先贴出解决方案,至于原因,感兴趣的可以继续往下看
1、解决方案
只要将函数声明放在下面指示的地方即可
#ifdef __cplusplus
extern "C" {
#endif
// 你的函数声明
#ifdef __cplusplus
}
#endif
2、解决思路
最初的思路
根本原因在于 .c 文件和 .cpp 文件都引入了 Add.h,然而生成符号表的时候,却是使用了不同的命名规则,这就导致了得到了不同的符号名。
我们要保证的是,无论是 C 还是 C++ 编译器,得到的符号名是一样的,因此就引入了 extern "C",extern "C"可以保证自身作用域里的内容,是按照 C 编译器的修饰规则去编译的,这样的话得到的符号名就都是 C编译器的编译出来的结果。
extern "C" {
int Add(int x, int y);
}
偶遇小坑
预处理阶段 Add.c 引入头文件 Add.h 并展开的时候,就变成了如下的样子,我们发现报错了,这是因为 C 程序虽然可以识别 extern 关键字,但是无法识别 extern "C" 这种形式。
填坑!完事!
.cpp 文件和 .c 文件都会引入 Add.h 文件,cpp文件默认使用的是 C++编译器,而 c 文件默认使用的是C编译器,那我们的 Add.h 就有必要自己识别当前自己是在 cpp 文件被展开,还是在 c 文件被展开。
这就要用到宏 __cplusplus,C++中定义了该宏,C却没有定义,因此如果在 cpp 文件中被展开,就需要加上 extern "C"; 如果是在 c 文件中被展开,就无需加上。
#ifdef __cplusplus
extern "C" {
#endif
int Add(int x, int y);
#ifdef __cplusplus
}
#endif