废话不多说,直接来看代码。
首先建了一个工程,主函数在cpp文件里,另外用c和c++语言各自实现了一个加法函数add。注意两个源文件的后缀,一个为“.c”,一个为“.cpp”。工程目录文件如下:
主函数如下:
#include <stdio.h>
#include <iostream>
#include "cfunction.h"
#include "cplusfunction.h"
int main(int argc, char* argv[])
{
std::cout<<add(4, 5)<<std::endl;
system("pause");
return 0;
}
我们在c++的main函数里调用c实现的整型加法函数,结果编译时报错如下:
其中,__cdecl是C和C++默认的调用约定,这个与主题无关。
这个报错的意思是add这个函数在被编译器编译后函数名称变成了?add@@YAHHH@Z这个符号,而在链接的过程中,main函数去找这个符号的时候找不到。
下面我们给这个函数加上extern “C”。 cfunction.h如下
/*****************************************************
Author: zzh
Mail: 791745123@qq.com
Time: 2019-2-17
Function:
c函数头文件
Version: v 1.0
*****************************************************/
//写法一
#ifdef __cplusplus
extern "C"{
#endif
int add(int num1, int num2);
#ifdef __cplusplus
};
#endif
//写法二
//extern "C" int add(int num1, int num2);
写法一,即如果当前编译单元是c++代码,那么add这个函数就会在extern “C”里声明。写法二则比较直接。
再编译则成功。
为什么加上extern “C”就可以了呢?
我们首先要知道,函数和变量在编译后它们的名称都会转换成一个唯一符号,然后在链接的过程中,调用者根据这个唯一的符号去调用,这种符号是按照一定约定生成的。(详情参考《程序员的自我修养》第三章。)
windows下的编译器会在c语言符号(变量和函数)前加上“_”,即add函数被编译后的符号为_add。
下面来验证一下。
把C实现的add的定义注释掉,再编译就能看到add被编译后的符号。
报错信息如下
可以看到,符号为“_add”。与之前的报错不同。
Vs编译器下,c++函数编译后的符号规则可参考《程序员的自我修养》第三章。
因此,当我们在c++的编译环境中调用c实现的函数时。在编译过程中,c函数add编译后的符号为“_add”,而在c++的main中,add被编译为“?add@@YAHHH@Z”。两个符号不一致,所以就会在链接过程中报错。
一旦将add在extern “c”中声明,在c++的main中,add就被编译为“_add”。这样符号一致,链接器就能找到了。
利用extern “c”使c兼容c++的做法很常见,尤其是在c的库文件中。我们可以打开“stdio.h”这个头文件查看一下。
这样一来,在我们这个工程里调用这个c的库函数就不会报错了。
调用约定相关介绍请看:https://blog.csdn.net/luoweifu/article/details/52425733