C++文件调用C语言里定义的函数/extern声明外部函数

客舟上 照月华
一点星子别残霞
杯中泪 江心洒
身似浮萍逐浪花
—《玉珠花》

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. 解决方案

  1. 首先,你要有一个共同的头文件:head.h
    #ifndef __HEAD__
    #define __HEAD__
    	#ifdef __cplusplus
    	extern "C"{
    	#endif
    		int fun(int a, float b, int c);
    	#ifdef __cplusplus
    	}
    	#endif
    #endif
    
  2. 在两个文件里面分别包含head.h

为什么是这么写:
首先,#ifndef __HEAD__ #define __HEAD__这个没啥好说的,就是防止头文件重复包含
主要是#ifdef __cplusplusextern "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文件里不需要额外声明,因为头文件被展开了。这个写法也是标准写法,希望读者记住。

怎敢忘记花楼下
少年风流思无涯
转身交错那一刹
姑娘掉落玉珠花
—《玉珠花》

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值