C++调用C语言之extern “C“简析

我的这篇博客是紧接着上一篇博客《C/C++中的分文件编写》来记录的。
在《C/C++中的分文件编写》中,我们举的例子中main函数和被调用的函数在不同的文件中,但是main函数所在的文件和被调用函数所在的文件都是C语言文件或者都是C++文件,那如果我的main函数所在的文件是.cpp文件而我的被调用函数所在的文件是.c文件又会怎么样呢?

myStrlen.h:
#pragma once
#include <stdio.h>
extern int myStrlen(char* myStr);

myStrlen.c:
#include <stdio.h>
int myStrlen(char* myStr)
{
    int len = 0, i = 0;
    while(myStr[i] != '\0')
    {
        len++;
        i++;
    }
    return len;
}

main.cpp:
#include <stdio.h>
#include "myStrlen.h"

int main(int argc, char* argv[])
{
    char* myStr = (char*) "ShiGuangluo"; // C++中也需要强制类型转换 否则会报warning
    int len = myStrlen(myStr);
    printf("len = %d\n", len);
}

这段代码如果在linux或者windows下编译,报的错误信息如下(在vs中编译的):

错误    1    error LNK2019: 无法解析的外部符号 "int __cdecl myStrlen(char *)" (?myStrlen@@YAHPAD@Z),该符号在函数 _main 中被引用    C:\myProgramFile\vsCode\vsTest\vsTest\main.obj    vsTest
错误    2    error LNK1120: 1 个无法解析的外部命令    C:\myProgramFile\vsCode\vsTest\Debug\vsTest.exe    1    1    vsTest

这个错误信息实际上是表示在链接阶段找不到myStrlen()函数。为什么找不到呢?其实是因为C++允许函数重载,因此在编译阶段,编译器会将函数名修改掉而C语言不会,因此C++去调用C语言函数的时候就会发生找不到的情况。
解决方法就是在.h文件中声明这个函数是C语言的函数,这样子编译器就不会去改名了。头文件中的声明方法如下:

#ifdef __cplusplus   // 注意这里是两个下划线
extern "C" {
#endif

...... // 在这里添加函数声明

#ifdef __cplusplus
}
#endif

按照这种方式去修改myStrlen.h文件:

myStrlen.h:
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>
extern int myStrlen(char* myStr);

#ifdef __cplusplus
}
#endif

extern C的原理如下:函数的编译分成调用处编译和实现处编译两部分(其实也就是指调用和实现在两个不同的文件中),只有在函数调用处编译出来的符号表函数名称和函数实现处编译出来的符号表函数名称相同,链接的时候才能够找到这个函数,否则就会报出:"undefined reference"或者“一个无法解析的外部链接”这样的错误。
如果你的函数是一个C语言函数(这个函数实现在.c文件中),那么函数实现处编译肯定是不会给这个函数改名的,因此符号表中的这个函数符号名称还是原来的函数名。此时如果你的调用处也是一个C文件,那么调用处的编译也是使用C语言编译器,所以调用处的符号表名称也不会修改,这样调用处符号表函数名称和实现处符号表函数名称相同,链接不会有任何问题!
但如果你的函数调用处是一个C++文件,那么调用处的编译就使用的是C++编译器,因此C++编译器在调用处编译的时候肯定会将你这个函数在符号表中的函数名称给修改掉,这样子调用处的符号表函数名称和实现处的符号表函数名称完全不同,链接的时候肯定找不到,就一定会报错!
解决办法就是上面说的加extern “C”{} ,将你的函数声明放在括号里面,这个关键字就是告诉C++编译器,在这个函数调用处编译的时候不要给这个函数在符号表中改名。
但是C语言里面没有这个extern “C”{}关键字(因为C语言中本来就是不改名的,就不需要这个关键字声明),如果直接这么写的话,C语言函数调用的时候就会报错了。怎么办呢?办法就是借用__cplusplus做条件编译。__cplusplus与编译器有关,如果调用处是C++编译器,这个宏就是被定义的,如果调用处是C语言编译器,这个宏就是没有被定义的。这样子就完成了兼容C与C++都可以对这个函数实现正确调用。
另外需要注意的一点是:extern “C"只能用于对于C语言函数添加,如果你对一个C++函数添加就也会导致找不到这个C++函数实现!所以说,extern “C"不加(指C++调用C语言函数的时候,不给C语言函数加)会导致"undefined reference”,加了(指C++调用C++函数的时候,给被调用的C++函数也加了)也可能会导致"undefined reference”!

另外在编译过程中,如果某一个函数出现undefined reference,其实可以通过使用objdump检查出是否是由于没加extern "C"导致的。
对于动态库,可以使用-T这个参数导出整个动态库的符号表:objdump libxxx.so -T
对于某一个.o文件,可以使用-t这个参数导出.o文件的符号表:objdump xxx.o -t
然后对比调用和函数实现处的符号表中的函数名是否相同,如果相同就是肯定可以找到的,如果不同就说明应该是某个地方没有加extern "C"或者多加了extern "C"导致符号表中的函数名不一致。

下面就是一个多加了extern "C"的例子:
我的工程中有一个函数void SV_DVR_SetScreenFlag(),这个函数实现在libsv_main.so库中,可以执行objdump libsv_main.so -T > main.txt,如下图所示:
在这里插入图片描述
此函数的调用在libsv_gui.so这个动态库中,我们导出libsv_gui.so的符号表(当然也可以直接导出这个函数实现文件xxx.o的符号表):objdump libsv_gui.so -T > gui.txt,如下图所示:
在这里插入图片描述
对比上面的两个文件,找不到是肯定的了,因为函数实现在编译的时候被改名了,但是函数调用处却没有被改名,名字不一样肯定找不到!
原因分析如下:由于调用处和函数实现处都是C++文件,因此不需要加入extern “C”,但是这里函数声明的时候加入了,导致调用的时候找不到函数实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值