C和C++混合编译,extern"C"的用法

C/C++ 混合编译 C++中的extern “C“ C/C++ 混合编译 C++中的extern “C” gcc编译*.c和*.cpp文件依据各自的文件类型各自编译为C型目标文件和C++类型目标文件 g++编译*.c\*.cpp文件,都是编译为C++类型的目标文件 使用g++时,不论是*.c还是*.cpp都将链接std c++库。而gcc是依据各自文件类型链接相应的std c库或者是std c++库 当一个程序有通过g++编译的C++目标模块与通过gcc编译的C目标模块时,链接会报错。案例如下: test.c int sum(int a,in 阅读详情

关于extern_C 
通常,在C语言的头文件中经常可以看到类似下面这种形式的代码

#ifdef  __cplusplus  
extern "C" {  
#endif  


/**** some declaration or so *****/  


#ifdef  __cplusplus  
}  
#endif  /* end of __cplusplus */  

那么,这种写法什么用呢?实际上,这是为了让CPP能够与C接口而采用的一种语法形式。之所以采用这种方式,是因为两种语言之间的一些差异所导致的。由于CPP支持多态性,也就是具有相同函数名的函数可以完成不同的功能,CPP通常是通过参数区分具体调用的是哪一个函数。在编译的时候,CPP编译器会将参数类型和函数名连接在一起,于是在程序编译成为目标文件以后,CPP编译器可以直接根据目标文件中的符号名将多个目标文件连接成一个目标文件或者可执行文件。但是在C语言中,由于完全没有多态性的概念,C编译器在编译时除了会在函数名前面添加一个下划线之外,什么也不会做(至少很多编译器都是这样干的)。由于这种的原因,当采用CPP与C混合编程的时候,就可能会出问题。假设在某一个头文件中定义了这样一个函数:

int foo(int a, int b);  

 

而这个函数的实现位于一个.c文件中,同时,在.cpp文件中调用了这个函数。那么,当CPP编译器编译这个函数的时候,就有可能会把这个函数名改成_fooii,这里的ii表示函数的第一参数和第二参数都是整型。而C编译器却有可能将这个函数名编译成_foo。也就是说,在CPP编译器得到的目标文件中,foo()函数是由_fooii符号来引用的,而在C编译器生成的目标文件中,foo()函数是由_foo指代的。但连接器工作的时候,它可不管上层采用的是什么语言,它只认目标文件中的符号。于是,连接器将会发现在.cpp中调用了foo()函数,但是在其它的目标文件中却找不到_fooii这个符号,于是提示连接过程出错。extern “C” {}这种语法形式就是用来解决这个问题的。本文将以示例对这个问题进行说明。

首先假设有下面这样三个文件:

 /* file: test_extern_c.h */  

#ifndef __TEST_EXTERN_C_H__  

#define __TEST_EXTERN_C_H__  

#ifdef  __cplusplus  
extern "C" {  
#endif  

/*  
 * this is a test function, which calculate  
 * the multiply of a and b.  
 */  

extern int ThisIsTest(int a, int b);  

#ifdef  __cplusplus  
}  
#endif  /* end of __cplusplus */  

#endif  

在这个头文件中只定义了一个函数,ThisIsTest()。这个函数被定义为一个外部函数,可以被包括到其它程序文件中。假设ThisIsTest()函数的实现位于test_extern_c.c文件中:

/* test_extern_c.c */  

#include "test_extern_c.h"  

int ThisIsTest(int a, int b)  
{  
    return (a + b);  
}  

可以看到,ThisIsTest()函数的实现非常简单,就是将两个参数的相加结果返回而已。现在,假设要从CPP中调用ThisIsTest()函数:

/* main.cpp */  
#include "test_extern_c.h"  
#include <stdio.h>  
#include <stdlib.h>  

class FOO {  
public:  

int bar(int a, int b)  
{  
    printf("result=%i\n", ThisIsTest(a, b));  
}  
};  

int main(int argc, char **argv)  
{  
    int a = atoi(argv[1]);  
    int b = atoi(argv[2]);   

    FOO *foo = new FOO();  

    foo->bar(a, b);  

    return(0);  
}  
  • 在这个CPP源文件中,定义了一个简单的类FOO,在其成员函数bar()中调用了ThisIsTest()函数。下面看一下如果采用gcc编译test_extern_c.c,而采用g++编译main.cpp并与test_extern_c.o连接会发生什么情况:
[cyc@cyc src]$ gcc -c test_extern_c.c  

[cyc@cyc src]$ g++ main.cpp test_extern_c.o  

[cyc@cyc src]$ ./a.out 4 5                  

result=9

可以看到,程序没有任何异常,完全按照预期的方式工作。那么,如果将test_extern_c.h中的extern “C” {}所在的那几行注释掉会怎样呢?注释后的test_extern_c.h文件内容如下:

 /* test_extern_c.h */  
#ifndef __TEST_EXTERN_C_H__  
#define __TEST_EXTERN_C_H__  

//#ifdef   __cplusplus  
//extern "C" {  
//#endif  

/*  
 * this is a test function, which calculate  
 * the multiply of a and b.  
 */  

extern int ThisIsTest(int a, int b);  


//#ifdef   __cplusplus  
//  }  
//#endif   /* end of __cplusplus */  

#endif  

除此之外,其它文件不做任何的改变,仍然采用同样的方式编译test_extern_c.c和main.cpp文件:

[cyc@cyc src]$ gcc -c test_extern_c.c  

[cyc@cyc src]$ g++ main.cpp test_extern_c.o  

/tmp/cca4EtJJ.o(.gnu.linkonce.t._ZN3FOO3barEii+0x10): In function `FOO::bar(int, int)':  

: undefined reference to `ThisIsTest(int, int)'  

collect2: ld returned 1 exit status  

在编译main.cpp的时候就会出错,连接器ld提示找不到对函数ThisIsTest()的引用。

为了更清楚地说明问题的原因,我们采用下面的方式先把目标文件编译出来,然后看目标文件中到底都有些什么符号:

[cyc@cyc src]$ gcc -c test_extern_c.c      

[cyc@cyc src]$ objdump -t test_extern_c.o  

test_extern_c.o:     file format elf32-i386  

SYMBOL TABLE:  

00000000 l    df *ABS*  00000000 test_extern_c.c  

00000000 l    d  .text  00000000  

00000000 l    d  .data  00000000  

00000000 l    d  .bss   00000000  

00000000 l    d  .comment       00000000  

00000000 g     F .text  0000000b ThisIsTest  
[cyc@cyc src]$ g++ -c main.cpp            
[cyc@cyc src]$ objdump -t main.o          
main.o:     file format elf32-i386  
SYMBOL TABLE:  

00000000 l    df *ABS*  00000000 main.cpp  

00000000 l    d  .text  00000000  

00000000 l    d  .data  00000000  

00000000 l    d  .bss   00000000  

00000000 l    d  .rodata        00000000  

00000000 l    d  .gnu.linkonce.t._ZN3FOO3barEii 00000000  

00000000 l    d  .eh_frame      00000000  

00000000 l    d  .comment       00000000  

00000000 g     F .text  00000081 main  

00000000         *UND*  00000000 atoi  

00000000         *UND*  00000000 _Znwj  

00000000         *UND*  00000000 _ZdlPv  

00000000  w    F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii  

00000000         *UND*  00000000 _Z10ThisIsTestii  

00000000         *UND*  00000000 printf  

00000000         *UND*  00000000 __gxx_personality_v0  
  • 可以看到,采用gcc编译了test_extern_c.c之后,在其目标文件test_extern_c.o中的有一个ThisIsTest符号,这个符号就是源文件中定义的ThisIsTest()函数了。而在采用g++编译了main.cpp之后,在其目标文件main.o中有一个_Z10ThisIsTestii符号,这个就是经过g++编译器“粉碎”过后的函数名。其最后的两个字符i就表示第一参数和第二参数都是整型。而为什么要加一个前缀_Z10我并不清楚,但这里并不影响我们的讨论,因此不去管它。显然,这就是原因的所在,其原理在本文开头已作了说明。

[cyc@cyc src]$ gcc -c test_extern_c.c  

[cyc@cyc src]$ objdump -t test_extern_c.o  

test_extern_c.o:     file format elf32-i386  

SYMBOL TABLE:  

00000000 l    df *ABS*  00000000 test_extern_c.c  

00000000 l    d  .text  00000000  

00000000 l    d  .data  00000000  

00000000 l    d  .bss   00000000  

00000000 l    d  .comment       00000000  

00000000 g     F .text  0000000b ThisIsTest  
  • 那么,为什么采用了extern “C” {}形式就不会有这个问题呢,我们就来看一下当test_extern_c.h采用extern “C” {}的形式时编译出来的目标文件中又有哪些符号:
[cyc@cyc src]$ g++ -c main.cpp  

[cyc@cyc src]$ objdump -t main.o  

main.o:     file format elf32-i386  

SYMBOL TABLE:  

00000000 l    df *ABS*  00000000 main.cpp  

00000000 l    d  .text  00000000  

00000000 l    d  .data  00000000  

00000000 l    d  .bss   00000000  

00000000 l    d  .rodata        00000000  

00000000 l    d  .gnu.linkonce.t._ZN3FOO3barEii 00000000  

00000000 l    d  .eh_frame      00000000  

00000000 l    d  .comment       00000000  

00000000 g     F .text  00000081 main  

00000000         *UND*  00000000 atoi  

00000000         *UND*  00000000 _Znwj  

00000000         *UND*  00000000 _ZdlPv  

00000000  w    F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii  

00000000         *UND*  00000000 ThisIsTest  

00000000         *UND*  00000000 printf  

00000000         *UND*  00000000 __gxx_personality_v0  
  • 注意到这里和前面有什么不同没有,可以看到,在两个目标文件中,都有一个符号ThisIsTest,这个符号引用的就是ThisIsTest()函数了。显然,此时在两个目标文件中都存在同样的ThisIsTest符号,因此认为它们引用的实际上同一个函数,于是就将两个目标文件连接在一起,凡是出现程序代码段中有ThisIsTest符号的地方都用ThisIsTest()函数的实际地址代替。另外,还可以看到,仅仅被extern “C” {}包围起来的函数采用这样的目标符号形式,对于main.cpp中的FOO类的成员函数,在两种编译方式后的符号名都是经过“粉碎”了的。

因此,综合上面的分析,我们可以得出如下结论:采用extern “C” {} 这种形式的声明,可以使得CPP与C之间的接口具有互通性,不会由于语言内部的机制导致连接目标文件的时候出现错误。需要说明的是,上面只是根据我的试验结果而得出的结论。由于对于CPP用得不是很多,了解得也很少,因此对其内部处理机制并不是很清楚,如果需要深入了解这个问题的细节请参考相关资料。

备注: 
1. 对于要在cpp中使用的在c文件中写好的函数func(),只需要在c文件的头文件中添加extern “C”声明就可以了。比如:extern “C” func() { …}

当然,可以使用

#ifdef __cplusplus

extern "C" {

#endif

和


#ifdef __cplusplus

}

#endif
  • 将整个c文件的函数全都括起来。
C++和C的混合编译 简介 C++ 语言的创建初衷是 “a better C”,但是这并不意味着 C++ 中类似 C 语言的全局变量和函数所采用的编译和连接方式与 C 语言完全相同。作为一种欲与 C 兼容的语言, C++ 保留了一部分过程式语言的特点(被世人称为"不彻底地面向对象"),因而它可以定义不属于任何类的全局变量和函数。但是, C++ 毕竟是一种面向对象的程序设计语言,为了支持函数的重载, C++ 对全局函数的处理方式与 C 有明显的不同。 本文将介绍如何通过 extern “C” 关键字在 C++ 中支持 C 语言 和 阅读详情

相关推荐

OneOS远程升级能力实操演示 | OTA升级

OneOS远程升级能力实操演示,差分算法提升效率,OTA升级,OTA差分算法,全量升级、压缩升级、差分升级,有线网络连接,bootloader,升级平台,在线差分包制作,触摸屏以及GUI图形操作

OneOS是中国移动针对物联网领域推出的轻量级操作系统 2009

C/C++混合编程--extern “C” 使用方法详解

**其实在网上关于这个问题已经讨论很多了,但是大多都是重复的,确实讲解的很详细,还指出了怎么用是错误的,本来记忆就不怎么样,所以对于记忆这些错误的用法更是讨厌,还不如记忆一种通用的而且比较规范、代码阅读起来比较舒适的方法,下面我们开始吧!!! C、C++密不可分,平时使用更多的是C,但有时候却少不了C++,而且是C、C++混搭(混合编程)在一起的,比如,RTP视频传输,live555多媒体播放等都

bestkilly的博客 1132

MTK-Display技术解析:从MDP到DDP的架构演进

本文深入解析了联发科(MTK)显示技术从MDP到DDP的架构演进。MDP作为多媒体数据预处理中心,负责格式转换与缩放;而DDP则整合了OVL、COLOR、AAL等模块,并引入CMDQ与场景化模式,实现了从分散处理到统一智能调度的飞跃,旨在提升画质、流畅度并降低功耗。

weixin_29218509的博客 253

C++ -- extern “C“ 用法

目录 一、extern "C"是什么? 二、用法展示 1.创建静态库 2.在c++文件中调c的静态库代码 3.在C文件中调c++的静态库代码 总结 一、extern "C"是什么? extern "C"是为了在c++中兼容c代码,表示extern "C"代码中是标准的c 语言代码,要用c的编译器来编译,而不是用c++的编译器来编译 二、用法展示 1.创建静态库 创建一个空项目,我这里命名为DS,然后在属性>>常规中把配置类型改成静态库类...

xd6905的博客 2741

IEEE Access投稿全流程解析:从初次提交到最终接收的完整指南

本文详细解析了IEEE Access期刊从初次投稿到最终接收的全流程,包括ORCID注册、论文投稿七步法、回稿再投流程及最终版提交等关键步骤。特别强调了4-6周快速同行评审的特点,为研究者提供了一份实用的投稿指南,帮助高效完成IEEE Access期刊投稿。

weixin_29273849的博客 913

非华为设备实现多屏协同:最新电脑管家破解安装指南

本文提供了在非华为电脑上安装华为电脑管家并实现多屏协同功能的详细破解指南。通过绕过硬件检测机制,用户可在联想、戴尔等设备上体验与华为生态类似的无缝文件拖拽、跨屏操作等功能。文章涵盖了从系统准备、工具下载、破解安装到手机连接及故障排查的全流程,助您打破设备壁垒,提升办公效率。

weixin_29209805的博客 1131

extern"C", C和C++混合编译

转自C和C++混合编译,extern"C"的用法 https://blog.youkuaiyun.com/monroed/article/details/54880944

lynnlycs的专栏 313

ACL访问控制列表详解:原理、配置与应用

如果发现ACL规则没有达到预期效果,需要检查ACL规则配置是否正确,是否在正确的接口和方向上应用,以及规则顺序是否合理(ACL规则按顺序匹配,匹配到即停止)。其中,rule-id是规则的编号,用于确定规则的匹配顺序;例如,应该先配置针对特定主机和特定端口的规则,再配置针对整个网段的规则,最后配置允许所有流量的规则,这样才能确保ACL按照预期工作,避免因规则顺序不当导致的访问控制失效。例如,应该先配置针对特定主机的规则,再配置针对整个网段的规则,最后配置允许所有流量的规则,这样才能确保ACL按照预期工作。

彭大帅的博客 1045

嵌入在C++程序中的extern "C"

嵌入在C++程序中的extern "C" 1.extern的作用 extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,可以告知编译器,用extern声明的函数和变量可以在本模块或其它模块中使用。 通常,在模块的头文件中对本模块提供给其它模...

dichuangheng8094的博客 145

SAP Workflow 工作流开发步骤总结

SAP WORKFLOW

qq_36952606的博客 5088

C++与C混合编译头文件陷阱全解析(资深架构师20年实战经验总结)

解决C与C++混合编译兼容难题,深入剖析混合编译的头文件常见陷阱与正确写法。涵盖extern "C"用法、头文件卫士设计、语言链接声明等关键技巧,适用于跨语言项目集成。提升代码稳定性,避免链接错误,值得收藏。

CompiTide的博客 773

STM32 cubeMX配置串口重定向

这篇文章主要讲解什么是串口重定向,为什么要串口串口重定向。使用printf函数和上位机通信是非常的方便的。希望大家都能够掌握。

m0_49476241的博客 4570

【21. extern “C“ , C和C++混合编译详解】

在实际的开发中,我们往往会遇到C和C++混合编译的场景。例如,使用C语言编写的库需要被C++程序调用,或者将C++代码与现有的C代码库集成。在这些情况下,C和C++之间的差异,尤其是函数的名称修饰(name mangling)和链接方式,会导致编译和链接错误。本文将详细介绍如何通过extern "C"解决这一问题,确保C和C++代码能够顺利互操作。C和C++语言在函数声明和链接的方式上存在差异,主要表现为C++的名称修饰机制。为了在C++中调用C语言的函数,必须使用extern "C"

涛ing的博客 1102

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【c/c++】c和cpp混合编译

在这段代码中,#ifdef __cplusplus 和 #endif 之间的代码是为了在 C++ 中使用 C 语言的函数声明和定义时,确保编译器正确地处理 C 和 C++ 之间的语法差异。这是因为 C 和 C++ 有一些不同之处,包括函数名的重载、类型转换等。在这段代码中,extern “C” 是一个 C++ 的特性,它告诉编译器要按照 C 的方式来处理其中的函数。:这个条件编译指令检查是否正在编译 C++ 代码。__cplusplus 是一个宏,当编译器编译 C++ 代码时,它会被定义。

Accowo的博客 3844

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

ESP32的IDF实现C语言和C++语言的混合编译

ESP32C/C++混合编译

weixin_56634270的博客 2767

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

C++中extern关键字使用

chapter 1 . extern关键字的作用     extern是一个关键字,它告诉编译器存在着一个变量或者一个函数,如果在当前编译语句的前面中没有找到相应的变量或者函数,也会在当前文件的后面或者其它文件中定义,来看下面的例子。     // extern.cpp : Defines the entry point for the console application. // #

sruru的专栏 9万+

从C到C++ | extern关键字

摘要:extern关键字在C/C++中主要有三种用途:1. extern "C"用于解决C++和C语言之间的函数名兼容问题,通过禁止C++的名称修饰实现互调;2. extern修饰函数/变量声明,表示其定义在其他文件中,实现多文件共享;3. extern "C"修饰变量使C++变量按C规则处理,实现跨语言变量访问。其中extern "C"与函数重载冲突,使用时需注意声明与定义的区别,推荐通过条件编译编写兼容头文件。extern声明变量时需严格区分声

weixin_65043441的博客 1043

如何混合编译C语言和C++

如何混合编译C语言和C++ 实际开发过程中,C++中会调用C与语言编写的代码,我在网络上面找到一篇写得很好的文章 http://blog.youkuaiyun.com/keensword/article/details/401114 就着上面的例子,我使用C语言采用两种方法重写了一下。 方法一、全局函数和变量在devVar.c文件中实现,在extern.cpp文件中使用ex...

坚持 1989

是否能在keil中混合编译c和c++程序

keil中支持混合编译C和C++程序, 因为其本质最终都是编译成汇编,所以是可以同时操作的。  在混合编译时,需要注意以下几点: 1、C文件扩展名必须为.C, C++文件扩展名必须为.CPP, 头文件扩展名必须为.CPP。 如果源文件的扩展名使用错误,会导致编译出错。 2、C语言的头文件,需要加上如下条件编译及限制代码: #ifdef  __cplusplus     extern "C"...

yuantuo3887的博客 6392

C与C++编译的区别 C和C++混合编译

原文链接,文章不错,修改了排版,看起来更加舒服。 关于extern_C 通常,在C语言的头文件中经常可以看到类似下面这种形式的代码: #ifdef __cplusplus extern "C" { #endif /**** some declaration or so *****/ #ifdef __cplusplus } #endif /* end of __cplusplus */ 那么,这种写法什么用呢?实际上,这是为了让CPP能够与C接口而采用的一种语法形式。之..

hpuzsk的博客 3488
上一篇: 什么是BQB认证?
下一篇: 压缩算法效率比较
evilcode
博客等级 码龄20年 307粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值