客舟上 照月华
一点星子别残霞
杯中泪 江心洒
身似浮萍逐浪花
—《玉珠花》
1. 问题背景
现有cpp文件main.c,需要调用C文件fun.c里定义的函数fun(),应该如何声明该函数?
2. 为什么不能直接调用
因为cpp为了支持函数重载等功能,cpp编译器会对编译后产生的函数加上独特的函数修饰符,例如你在cpp里声明一个int fun(int a, float b, int c)
,为了能够正确的链接对应函数,编译器会为函数产生一个独一无二的函数特征描述符,例如,在C++编译器下:
int fun(int a, float b, int c);
产生的符号是:?fun@@YAHHMH@Z
,怎么看这个符号?你故意声明这个函数但是不定义, 编译看报错就可以:
#include <iostream>
int fun(int a, float b, int c);
int main()
{
fun(1, 2, 3);
return 0;
}
报错:
[build] test.obj : error LNK2019: 无法解析的外部符号 “int __cdecl fun(int,float,int)” (?fun@@YAHHMH@Z),函数 main 中引用了该符号 [E:\program\c++\test2\build\test.vcxproj]
[build] E:\program\c++\test2\build\Debug\test.exe : fatal error LNK1120: 1 个无法解析的外部命令 [E:\program\c++\test2\build\test.vcxproj]
可以看到,?fun@@YAHHMH@Z
就是编译器产生的函数特征标识符。上面是cpp文件编译的,也就是cpp编译器,我们把源文件命名为C文件,重新编译,提示:
[build] test.obj : error LNK2019: 无法解析的外部符号 fun,函数 main 中引用了该符号 [E:\program\c++\test2\build\test.vcxproj]
[build] E:\program\c++\test2\build\Debug\test.exe : fatal error LNK1120: 1 个无法解析的外部命令 [E:\program\c++\test2\build\test.vcxproj]
注意”无法解析的外部符号“后面的文字,这次变成了fun
,可见c语言编译器不会对函数做任何的修饰,函数标识符就是函数名。这个也能理解,毕竟C语言不支持重载,不需要标记函数的参数类型。
通过上面的实验,我们不难发现,C和Cpp编译器产生的函数标识符是不同的,如果你是两种语言混编,在C中定义的函数和在cpp中调用的函数符号完全不同,导致无法正确链接,从而报错。那么,如何解决这个问题呢?
3. 解决方案
- 首先,你要有一个共同的头文件:
head.h
#ifndef __HEAD__ #define __HEAD__ #ifdef __cplusplus extern "C"{ #endif int fun(int a, float b, int c); #ifdef __cplusplus } #endif #endif
- 在两个文件里面分别包含
head.h
为什么是这么写:
首先,#ifndef __HEAD__ #define __HEAD__
这个没啥好说的,就是防止头文件重复包含
主要是#ifdef __cplusplus
和extern "C"
。
#ifdef __cplusplus
:__cplusplus
是c++编译器预定义的宏,就是说,当这个文件是cpp编译器在编译的时候,这个宏存在,反之,当这个文件是c编译器编译的时候,就不存在。
extern "C"
:告诉链接器,链接的时候使用C语言规范链接函数
两个结合起来就是说,如果这个文件是cpp编译器在看,就会告诉他这个fun定义在c文件里,如果是c编译器在看,就和往常一样。
我们都知道,#include
命令本质上是展开命令,是把头文件整个替换到源代码里。这样的话,当你在编译main.cpp的时候,因为有__cplusplus
宏,编译器就知道了fun函数是由外部c文件定义的;当编译fun.c的时候,C语言编译器按照规定生成了函数符号,链接器链接的时候,因为有了extern c,就知道用C语言的规定来链接。
4. 错误写法举例
典型的错误写法就是只在cpp文件
里写,而共同的头文件里你仍然只是声明
head.h
int fun(int a, float b, int c);
main.cpp:
extern "C"{
int fun(int a, float b, int c);
}
这样会报什么错?会报函数重复定义错误。为什么?我们提到#include
命令是在此处展开,编译的时候,main.cpp成了这样:
main.cpp
int fun(int a, float b, int c);
extern "C"{
int fun(int a, float b, int c);
}
很显然,你先告诉编译器,有个fun,按照cpp规定编译,然后你又说,有个fun写在外部的c里面,你去那找,这样不就冲突了?所以不能在用到的cpp文件里面写,而应该在头文件声明的时候使用#ifdef _cplusplus
宏分情况指定。这一点网上很多博客写的是错误的,他们为了展示方便直接写在cpp文件里面,实际上根本就不对。我为什么要写这一篇博客就是因为被这个坑了。
5. 总结
当cpp文件里需要引用外部的定义在c文件里面的函数的时候,需要使用
#ifdef __cplusplus
extern "C"{
#endif
int fun(int a, float b, int c);
#ifdef __cplusplus
}
#endif 在头文件中来分情况指定函数声明。而cpp文件里不需要额外声明,因为头文件被展开了。这个写法也是标准写法,希望读者记住。<br>
怎敢忘记花楼下
少年风流思无涯
转身交错那一刹
姑娘掉落玉珠花
—《玉珠花》