C++和C的混合编译的项目实践

发布时间:2026/5/30 20:02:35

C++和C的混合编译的项目实践 简介C语言的创建初衷是 “a better C”但是这并不意味着C中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言C保留了一部分过程式语言的特点被世人称为不彻底地面向对象因而它可以定义不属于任何类的全局变量和函数。但是C毕竟是一种面向对象的程序设计语言为了支持函数的重载C对全局函数的处理方式与C有明显的不同。本文将介绍如何通过 extern “C” 关键字在C中支持C语言 和 在C语言中如何支持C。某企业曾经给出如下的一道面试题为什么标准头文件都有类似以下的结构123456789101112131415//head.h#ifndef HEAD_H#define HEAD_H#ifdef __cplusplusexternC{#endif/*...*/#ifdef __cplusplus}#endif#endif /* HEAd_H */问题分析这个头文件head.h可能在项目中被多个源文件包含#include “head.h”而对于一个大型项目来说这些冗余可能导致错误因为一个头文件包含类定义或inline函数在一个源文件中head.h可能会被#include两次如a.h头文件包含了head.h而在b.c文件中#include a.h和head.h——这就会出错在同一个源文件中一个结构体、类等被定义了两次。从逻辑观点和减少编译时间上都要求去除这些冗余。然而让程序员去分析和去掉这些冗余不仅枯燥且不太实际最重要的是有时候又需要这种冗余来保证各个模块的独立。为了解决这个问题上面代码中的1234#ifndef HEAD_H#define HEAD_H/*……………………………*/#endif /* HEAD_H */就起作用了。如果定义了HEAD_H#ifndef/#endif之间的内容就被忽略掉。因此编译时第一次看到head.h头文件它的内容会被读取且给定HEAD_H一个值。之后再次看到head.h头文件时HEAD_H就已经定义了head.h的内容就不会再次被读取了。那么下面这段代码的作用又是什么呢1234567#ifdef __cplusplusexternC{#endif/*.......*/#ifdef __cplusplus}#endif我们将在后面对此进行详细说明。关于 extern “C”前面的题目中的__cplusplus宏这是C中已经定义的宏是用来识别编译器的也就是说将当前代码编译的时候是否将代码作为C进行编译。首先从字面上分析extern “C”它由两部分组成extern关键字、“C”。下面我就从这两个方面来解读extern C的含义。首先被它修饰的目标是extern的其次被它修饰的目标是C的。extern关键字被 extern “C” 限定的函数或变量是extern类型的。extern是C/C语言中表明函数和全局变量作用范围可见性的关键字该关键字告诉编译器其声明的函数和变量可以在本模块或其它模块中使用。通常在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样模块B中调用模块A中的函数时在编译阶段模块B虽然找不到该函数但是并不会报错它会在连接阶段中从模块A编译生成的目标代码中找到此函数。被extern修饰的函数需要在编译阶段去链接该目标文件并且与extern对应的关键字是 static被static修饰的全局变量和函数只能在本模块中使用。因此一个函数或变量只可能被本模块使用时其一般是不可能被extern “C”修饰的。**注意**例如语句extern int a;仅仅是对变量的声明其并不是在定义变量a声明变量并未为a分配内存空间。定义语句形式为int a;变量a在所有模块中作为一种全局变量只能被定义一次否则会出现连接错误。被 extern “C” 修饰的变量和函数是按照C语言方式编译和连接的。由于C和C两种语言的亲密性并且早期大量的库都是由C语言实现的所以不可避免的会出现在C程序中调用C的代码、C的程序中调用C的代码但是它们各自的编译和链接的规则是不同的。函数名修饰由于Windows下vs的修饰规则过于复杂而Linux下gcc的修饰规则简单易懂下面我们使用了gcc演示了这个修饰后的名字。通过下面我们可以看出gcc的函数修饰后名字不变。而g的函数修饰后变成【_Z函数长度函数名类型首字母】。分别使用C的编译器和C的编译器去编译并获得一个可执行文件使用C语言gcc编译器编译后结果使用objdump -S 命令查看gcc生成的可执行文件使用C编译器g编译后结果使用objdump -S 命令查看g生成的可执行文件**linux**修饰后的函数名 _Z 函数名长度 形参类型首字母Windows下也是相似的细节上会有所不同本质上都是通过函数参数信息去修饰函数名。C的编译和链接方式采用g编译完成后函数的名字将会被修饰编译器将函数的参数类型信息添加到修改后的名字中因此当相同函数名的函数拥有不用类型的参数时在g编译器看来是不同的函数而我们另一个模块中想要调用这些函数也就必须使用C的规则去链接函数找修饰后的函数名才能找到函数的地址。C的编译和链接方式对于C程序由于不支持重载编译时函数是未加任何修饰的而且链接时也是去寻找未经修饰的函数名。C和C直接混合编译时的链接错误在C程序函数名是会被参数类型信息修饰的这就造成了它们之间无法直接相互调用。例如print(int)函数使用g编译时函数名会被修饰为 _Z5printi而使用gcc编译时函数名则仍然是print如果直接在C中调用使用C编译规则的函数会链接错误因为它会去寻找 _Z5printi而不是 print。【C和C的编译和链接方式的不同】参考C的函数重载extern“C”的使用extern C指令非常有用因为C和C的近亲关系。注意extern C指令中的C表示的一种编译和连接规约而不是一种语言。并且extern C指令仅指定编译和连接规约并不影响语义编译时仍是一个C的程序遵循C的类型检查等规则。对于下面的代码它们之间是有区别的1234externCvoidAdd(inta,intb);//指定Add函数应该根据C的编译和连接规约来链接externvoidAdd(inta,intb);//声明在Add是外部函数链接的时候去调用Add函数如果有很多内容要被加上extern “C”你可以将它们放入extern “C”{ }中。通过上面的分析我们知道extern C的真实目的是实现类C和C的混合编程在C源文件中的语句前面加上extern “C”表明它按照类C的编译和连接规约来编译和连接而不是C的编译的连接规约。这样在类C的代码中就可以调用C的函数or变量等。那么混合编译首先要处理的问题就是要让我们所写的C程序和C程序函数的编译时的修饰规则和链接时的修饰规则保持一致。

相关新闻