当前位置: 首页 > news >正文

c++中的extern “C“

在一些c语言的library库中,我们经常可以还看下面这样的结构

#ifndef __TEST_H
#define __TEST_H#ifdef _cplusplus
extern "C" {
#endif/*...*/#ifdef _cplusplus
}
#endif
#endif

#ifndef __TEST_H这样的宏定义应该是非常常见了,其作用是为了避免重复包含。

往下看,如果定义了_cplusplus宏,则添加extern "C"的标记,那么这个标记的作用是什么呢?

#ifdef _cplusplus
extern "C" {
#endif

这里首先给出答案,这是为了c/c++程序可以相互调用。下面就看看extern "C"是如何做到的。我们分两个场景,第一个场景就是c语言写的库,c和c++程序去调用。第二个场景就是c++写的库,c和c++程序去调用。

c写的库给c/c++调用

我们看第一个例子,在这个例子中,我们使用c语言构建了一个add函数,并提供了其头文件。我们要将该实现提供给c和c++的程序调用。

下面是该例子的目录结构。

.
├── add.c
├── add.h
├── main_c.c
├── main_cpp.cpp
└── makefile

add.h

#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H#ifdef __cplusplus
extern "C"{
#endifextern int add(int x,int y);#ifdef __cplusplus
}
#endif#endif

add.c

#include "add.h"
int add( int x, int y )
{return x + y;
}

main_cpp.cpp

#include "add.h"int main()
{add(2,3);return 0;
}

main_c.c

#include "add.h"int main()
{add(2,3);return 0;
}

makefile

all:main_cpp main_cmain_cpp: main_cpp.o add.o$(CXX)  -o $@ $^main_c: main_c.o add.o$(CC) -o $@ $^main.o: main_cpp.cpp$(CXX) -c -o $@ $<add.o: add.c$(CC)  -c -o $@ $<clean:rm -f *.o main_cpp main_c

使用make命令对上述模块进行构建。如果没有任何错误,那么恭喜你,add.o成功的被c和c++程序使用了。

我们知道c和c++编译器编译出来的符号名称是不同的,用c++的方式去寻找c语言的符号是无法寻找到的。extern "C"为何可以做到?

我们使用readelf -s add.o查看add.o的符号,可以看到add函数的名称就是add,这个就是典型c编译器编译出来的名字。

Symbol table '.symtab' contains 9 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS add.c2: 0000000000000000     0 SECTION LOCAL  DEFAULT    13: 0000000000000000     0 SECTION LOCAL  DEFAULT    24: 0000000000000000     0 SECTION LOCAL  DEFAULT    35: 0000000000000000     0 SECTION LOCAL  DEFAULT    56: 0000000000000000     0 SECTION LOCAL  DEFAULT    67: 0000000000000000     0 SECTION LOCAL  DEFAULT    48: 0000000000000000    20 FUNC    GLOBAL DEFAULT    1 add

我们再次查看readelf -s main_cpp.o | grep add去查看一下main_cpp中的符号表:

   72: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS add.c90: 0000000000400570    20 FUNC    GLOBAL DEFAULT   11 add

可以看到main_cpp中的符号表的名字也是add,因此main_cpp成功的找到了add函数。

但是我们知道c++的编译器在生成符号时,通常都会带上符号的参数类型(因为c++支持重载),例如下面的c++程序,编译之后,我们使用readelf查看符号表。

int add( int x, int y )
{return x + y;
}int main()
{add(2,3);return 0;
}

其输出的结果如下所示:

86: 0000000000400556    20 FUNC    GLOBAL DEFAULT   11 _Z3addii

可以看到add生成符号是_Z3addii。

因此,加与不加extern "C",add函数生成的符号名称是不同的。

看到这里,聪明的你已经大概知道extern "C"的作用了,就是修改了符号表的生成方式,将c++符号的生成方式换成了c的生成方式。

c库中生成的符号是c编译器的符号, 因此c语言可以直接链接。而c++程序需要使用extern "C"让编译器使用c的符号命名方式去进行链接,这样才能找到对应的符号。

c++写的库给c/c++调用

下面这个例子,我们使用c++语言构建了一个add函数,并提供了其头文件。我们要将该实现提供给c和c++的程序调用。

.
├── add.cpp
├── add.h
├── main_c.c
├── main_cpp.cpp
└── makefile

add.h

#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H#ifdef __cplusplus
extern "C"{
#endifextern int add(int x,int y);#ifdef __cplusplus
}
#endif#endif

add.cpp

#include "add.h"
int add( int x, int y )
{return x + y;
}

main_cpp.cpp

#include "add.h"int main()
{add(2,3);return 0;
}

main_c.c

#include "add.h"int main()
{add(2,3);return 0;
}

makefile

all:main_cpp main_cmain_cpp: main_cpp.o add.o$(CXX)  -o $@ $^main_c: main_c.o add.o$(CC) -o $@ $^main.o: main_cpp.cpp$(CXX) -c -o $@ $<add.o: add.cpp$(CXX)  -c -o $@ $<clean:rm -f *.o main_cpp main_c

其实这种场景和第一种场景是基本一致的。现在我们的库add.o是使用c++编译器生成的。

我们使用readelf查看其内容,可以看到其内容和之前c语言生成的库,add函数的符号是一样的。因为我们此时编译时使用了extern "C",也就是说使用c语言的符号构建方式进行编译。

Symbol table '.symtab' contains 9 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS add.c2: 0000000000000000     0 SECTION LOCAL  DEFAULT    13: 0000000000000000     0 SECTION LOCAL  DEFAULT    24: 0000000000000000     0 SECTION LOCAL  DEFAULT    35: 0000000000000000     0 SECTION LOCAL  DEFAULT    56: 0000000000000000     0 SECTION LOCAL  DEFAULT    67: 0000000000000000     0 SECTION LOCAL  DEFAULT    48: 0000000000000000    20 FUNC    GLOBAL DEFAULT    1 add

接下来链接的过程就和第一个场景一样了。

在本场景中,使用c++编写了一个包含add函数的模块,对其编译时,使用了c语言的符号构建方式。因此其符号表和c语言的库是相同的。最终链接时,由于加上了extern "C", 链接过程也将使用c语言的方式去寻找符号。

总结

  • extern "C"实际上就是告诉c++编译器去使用c编译器的规则去进行构建。
  • 在日常使用中,c++调用c库的使用频率更高一些,案例一就是这样的例子。
http://www.lryc.cn/news/89370.html

相关文章:

  • python异常处理名称整理
  • SpringMVC拦截器
  • Python第八章作业(初级)
  • chatgpt赋能python:Python中如何取消列表
  • Java中List排序的3种方法
  • flutter-读写二进制文件到设备
  • C语言基础知识:内存分配
  • 【Simulink】示波器图形数据导入Matlab重新绘图(论文)
  • 汇编调试及学习
  • Linux - 第19节 - 网络基础(传输层二)
  • web实现日历、阳历农历之间相互转换、npm、push、unshift、includes、innerHTML
  • GcExcel v6.1 支持新的 ‘.sjs‘ 模板文件 ‘.xltx‘ 格式 Crack
  • 面试官:MySQL自增主键一定是连续的吗?
  • 2023ACP世界大赛教育者论坛:让职业教育直面AI机遇与挑战
  • Unity基础 音频组件以及音频播放
  • SAP-MM-采购申请审批那些事!
  • 专业解读财务共享实现财务数智化转型的有效路径
  • 九章云极DataCanvas公司诚邀您共享AI基础软件前沿技术盛宴
  • 【高级语言程序设计(一)】第 10 章:文件
  • Android 宿主启动插件中的Activity和Service
  • 00后卷王自述,我真的很卷吗?
  • 真题详解(树的结点)-软件设计(八十四)
  • LDA算法实现鸢尾花数据集降维
  • 深入理解Linux虚拟内存管理
  • 自动化测试框架、Python面向对象以及POM设计模型简介
  • 【CSSpart4--盒子模型】
  • Linux - Java 8 入门安装与重装教程集锦
  • 2023年最新企业网盘排行榜出炉
  • C++内存分类
  • 不是说00后已经躺平了吗,怎么还是这么卷.....