C与C++混编中extern “C”的关键应用与注意事项

linbojue
• 阅读 5

在嵌入式系统开发中,C与C++的混编是一个常见的场景。然而,这种混编方式需要格外小心,特别是在处理extern "C"关键字时。这是因为C++在编译时会对函数名进行修饰(即所谓的mangling),而C语言则不会。这种差异可能导致链接错误,特别是在C++和C混编时,若不使用extern "C"进行声明,编译器可能会找不到正确的函数实现。因此,在混编时使用extern "C"是一种重要的编程技巧,它确保了C++编译器能够正确地链接到C语言编写的函数。

理解extern "C"的必要性 在C语言代码中,你是否曾遇到过这样的代码片段:

这看似无害的代码片段,可能会让你产生一种“一切正常,我们的代码一直这样写,从未遇到过问题”的错觉。然而,实际情况可能并非总是如此。这段代码与C++有着密切的联系。你或许已经注意到了cplusplus这个预定义宏,它是由C++规范所规定的。所有现代C++编译器都会预先定义这个宏,而C语言编译器则不会。按照规范,cplusplus的值应该等于199711L,但并非所有编译器都严格遵循这一规定,例如g++编译器就将它的值定义为1。因此,如果这段代码被C语言程序引用,其实际效果可能与预期大相径庭。

C与C++的名字空间差异 在C++编译环境中,存在一个被称为“名字粉碎”(name mangling)的神秘力量。当C++源文件被投入编译时,这个名字粉碎的力量便开始发挥作用,它会把源文件中出现的每个外部可见的名字都彻底改变,然后存入二进制目标文件的符号表中。

这个名字粉碎的存在,源于C++允许对同一名字进行不同定义,只要这些定义在语义上不产生二义性即可。例如,函数重载允许两个函数拥有相同的名字,只要它们的参数列表不同;甚至可以在不同的名字空间中声明完全相同的函数原型。这种灵活性为程序员提供了极大的便利,但也给编译器和链接器带来了不小的挑战。

此外,C++程序的构建方式仍然深受C语言的影响。编译器将每个源代码文件视为独立的编译单元,生成目标文件;随后,链接器通过查找这些目标文件的符号表来将它们链接成一个可执行程序。但C语言则截然不同,它是一门单一名字空间的语言,且不支持函数重载。这意味着在C语言的编译和链接过程中,同名对象是不被允许的。

链接规范与extern "C"的实际应用 为了应对上述问题,C++引入了链接规范(linkage specification)的概念,其表示法为extern "language string"。其中,"language string"可以是"C"或"C++",分别对应C语言和C++语言。这一规范的作用在于告知C++编译器,对于所有采用链接规范进行修饰的声明或定义,应依据所指定语言的相关规则进行处理,例如名称修饰、调用约定等。链接规范的运用方式灵活多样,主要包括两种。

单个声明的链接规范示例:


extern "C" void foo();

一组声明的链接规范示例:


extern "C" {

   void foo();
   int bar();
}

针对我们之前的示例,若将头文件my_handle.h的内容修改为上述形式,即可实现链接规范的正确应用。

在C与C++混编中避免错误 在深入探讨extern "C"的用法和限制后,我们回到最初的问题:为何#include指令不能置于extern "C"块中?为了解答这个问题,我们来看一个简单的示例。假设我们有a.h、b.h、c.h以及foo.cpp四个文件,其中foo.cpp包含了c.h,c.h又包含了b.h,而b.h最终包含了a.h。这样的包含关系形成了一个头文件的嵌套层次。

现在,我们使用C++编译器的预处理选项来编译foo.cpp文件,并观察其结果。

处理包含文件时的注意事项 值得注意的是,将#include指令置于extern "C"块中可能会引发不必要的链接规范冲突和潜在错误。在C++的规范中,extern "C" { }的嵌套是被允许的,但这种嵌套可能会带来一些难以预料的问题。

为了避免这些问题,我们可以简单地将#include指令移至extern "C" { }的外部。

这样的调整将确保编译过程不会出现问题,即便使用MSVC编译器。然而,将#include指令置于extern "C" { }内部也存在潜在风险,特别是在改变函数的链接规范方面。

实际案例分析和最佳实践 接下来,我们来看一个关于extern "C"的具体代码示例,以确认其写法的正确性。

在C++的规范中,__cplusplus的值被定义为199711L,这是一个非零值。尽管某些编译器并未严格遵循这一规范,但它们仍然确保了__cplusplus的非零值,至少在我所了解的范围内,尚未发现将其实现为0的编译器。因此,在大多数情况下,仅使用#ifdef __cplusplus ... #endif这种用法是冗余的。

然而,由于C++编译器的多样性,我们无法保证所有编译器或其早期版本都不会将__cplusplus定义为0。但只要我们能确保宏__cplusplus仅在C++编译器中预先定义,那么仅使用#ifdef __cplusplus ... #endif就足以明确我们的意图。 http://029github.wikidot.com/ http://028github.wikidot.com/ http://0931github.wikidot.com/ http://0871github.wikidot.com/ http://0477github.wikidot.com/

更一般的原则是,在充分理解原理和自身需求的基础上,我们应当在必要时才使用extern "C"声明,以便确保混编代码的兼容性和避免潜在问题。只要我们清楚自己在做什么,就有权利去尝试和探索。

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
GCC编译 C与C++ C89与C99
1)最初的ANSIC标准(X3.1591989)在1989年被批准,并于1990年发布。稍后这个标准被接受为ISO标准(ISO/IEC9899:1990)。虽然ISO标准将ANSI标准的某些章节重新编号并变为条款,但是两者实际上并无区别。不论是ANSI标准还是ISO标准,通常都称之为C89,偶尔也因为发布日
Wesley13 Wesley13
3年前
Java中的native关键字
一. 什么是NativeMethod  简单地讲,一个NativeMethod就是一个java调用非java代码的接口。一个NativeMethod是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern"C"告知C++编译器去
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
C++学习_从C到C++
一、引用的概念和应用 1.引用的概念下面写法定义了一个引用,并将其初始化为引用某个变量。类型名&引用名某变量名;intn4;int&rn;//r引用了n,r的类型是int&某个变量的引用,等价于这个变量,相当于该变量的一个别
Stella981 Stella981
3年前
C++ 匿名namespace的作用以及它与static的区别
一、匿名namespace的作用在C语言中,如果我们在多个tu(translationunit)中使用了同一个名字做为函数名或者全局变量名,则在链接阶段就会发生重定义错误,为了解决这个问题,我们可以在定义这些标识符(identifier)的时候加上static关键字修饰以限制它只在一个tu范围内可见。C继承了C语言中static关键
Wesley13 Wesley13
3年前
C++
第一眼见到explicit和volatile可能会一愣一愣的觉得可能是c11或者c14新加的标识符。其实不是这样,volatile和const两个关键字在C语言的第二个版本KRC的时候就被加入了C标准,他们是两个相对的关键字const修饰符表示这是一个常量类型,这个变量的值不会被程序改变volatile修饰符表示这个
Wesley13 Wesley13
3年前
C和C++函数时的JNI使用区别
Java调用C和C函数时的JNI使用区别:注意:jni.h头文件中对于\\\.c & \\\.cpp采用不同的定义在C的定义中,env是一个两级指针,而在C的定义中,env是个一级指针C形式需要对env指针进行双重deferencing,而且须将env作为第一个参数传给jni函数jclass(JNICALL\
Stella981 Stella981
3年前
C++ Modern C++
    现代的C,比较笼统。最近10多年的东西是否是现代的呢?我认为“时髦”这个词更准确一些。每个年代,时髦总是标新立异的,总是被年龄大一些的人看不惯的(虽然这些人也曾经“赶过时髦”)。ModernC就是用最时髦的东西去装饰您的代码。但是本质的东西还是没有变。改革初期,最时髦的服饰是喇叭裤,霹雳舞手套。那时没有智能手机,时髦的人扛着一台卡带
Wesley13 Wesley13
3年前
C语言与C++面试知识总结
!(https://oscimg.oschina.net/oscnet/898d005df67c3f7dd6f5975934cda6b99e4.png)这是一篇C语言与C面试知识点总结的文章,如果你觉得文章对你有帮助,文末右下角点个再看转发给更多的人。const作用1.修饰变量,说
小万哥 小万哥
1年前
深入浅出 C 语言:学变量、掌控流程、玩指针,全方位掌握 C 编程技能
C语言简介C语言介绍C语言的特性C语言相对于其他语言的优势C程序的编译C中的HelloWorld程序参考文章:学习变量、数据类型和运算符C中的变量和关键字C语言中的作用域规则C中的数据类型运算符及其类型C语言中的类型转换参考文章:了解控制流语句C语言中的循