C语言中的extern "C" 没那么简单!

 2024-02-21 02:03:10  阅读 0

extern "C"{  void foo();  int bar();}

对于我们前面的示例,如果我们将头文件 .h 的内容更改为:

c语言头文件的作用_c语音头文件_c.语言头文件

然后使用C++编译器重新编译.cpp,生成的目标文件.o中的符号表变为:

c语音头文件_c语言头文件的作用_c.语言头文件

由此我们可以看出,此时用“C”修饰的语句生成的符号与C语言编译器生成的符号是一致的。 这样,当你再次将.o和.o链接在一起时,就不会再出现之前的“符号未定义”错误了。

但此时如果重新编译.c,C语言编译器会报“语法错误”,因为“C”是C++的语法,C语言编译器不识别。 此时,宏可用于识别 C 和 C++ 编译器,正如我们之前讨论的那样。 修改后的.h代码如下:

c.语言头文件_c语音头文件_c语言头文件的作用

小心门后的未知世界

了解了“C”的由来和用途后,我们回到原来的话题,为什么不能把#指令放在“C”{...}里面呢?

我们先来看一个例子。 我们有ah、bh、ch和foo.cpp,其中foo.cpp包含ch,ch包含bh,bh包含ah,如下:

c.语言头文件_c语音头文件_c语言头文件的作用

现在使用C++编译器的预处理选项来编译foo.cpp,得到以下结果:

c语言头文件的作用_c.语言头文件_c语音头文件

正如您所看到的,当您将 # 指令放置在 "C" { } 内时,它将导致 "C" { } 的嵌套。 C++ 规范允许这种嵌套。 当发生嵌套时,最内层的嵌套优先。 例如,在以下代码中,函数 foo 将使用 C++ 链接规范,而函数 bar 将使用 C 链接规范。

c语言头文件的作用_c语音头文件_c.语言头文件

如果能够保证一个C语言头文件直接或间接依赖的所有头文件也是C语言,那么根据C++语言规范,这种嵌套应该没有问题。

但具体到某些编译器的实现,比如可能会因为“C”{ } 嵌套太深而报错。

不要为此责怪微软,因为就问题而言,这种嵌套是没有意义的。 您可以通过将 # 指令放在“C”{ } 之外来完全避免嵌套。

拿前面的例子来说,如果我们将每个头文件的#指令移到“C”{}之外,然后使用C++编译器的预处理选项来编译foo.cpp,我们将得到以下结果:

c.语言头文件_c语言头文件的作用_c语音头文件

这样的结果绝对不会导致编译问题——即使使用MSVC。

将 # 指令放入“C”{ } 内的另一个重大风险是您可能会无意中更改函数声明的链接规范。 例如:有两个头文件ah和bh,其中bh包含ah,如下:

c语音头文件_c语言头文件的作用_c.语言头文件

按照ah作者的初衷,函数foo是一个C++自由函数,它的链接规范是“C++”。 但在 bh 中,由于 # "ah" 被放置在 "C" { } 内,因此函数 foo 的链接规范被错误地更改。

由于每条#指令的背后都隐藏着一个未知的世界,除非你刻意去探索它,否则你永远不知道当你把#指令放在“C”{}里面会发生什么。 结果,会带来什么风险。

也许你会说,“我可以检查包含的头文件,我可以确保它们不会造成麻烦。” 但何苦呢? 毕竟,我们不必为不必要的事情付出代价,对吗?

问答

问:任何 # 指令都不能放在 "C" 内吗?

答:就像这个世界上的大多数规则一样,总会有特殊情况。

有时,你可能会利用头文件机制来“巧妙”地解决一些问题。 比如#pack的问题。 这些头文件具有与常规头文件不同的功能。 C 函数声明或变量定义不会放置在其中,并且链接规范不会影响其内容。 在这种情况下,您不必遵守这些规则。

更一般的原则是,当你明白了所有这些原则之后,只要你明白你在做什么,那就去做吧。

Q:你只是说 "C"不应该放进去,但是什么可以放进去呢?

答:链接规范仅用于修改函数和变量以及函数类型。 因此,严格来说,您应该只将这三种类型的对象放在“C”内。

但是,如果将 C 语言的其他元素,例如非函数类型定义(结构体、枚举等)放在“C”内部,则不会产生任何影响。 更不用说宏定义的预处理指令了。

因此,如果您重视良好的组织和管理习惯,则应该仅在必要时使用“C”语句。 即使你很懒,大多数情况下,将头文件本身的所有定义和声明放在“C”中也不会有什么大问题。

问:如果带有函数/变量声明的 C 头文件中没有 "C" 声明怎么办?

A:如果你可以判断这个头文件永远不能被C++代码使用,那就别管它了。

但现实是,在大多数情况下,你无法准确预测未来。 如果你现在加上这个“C”,不会花多少钱,但是如果你现在不加上,以后这个头文件不小心被包含在别人的C++程序里时,别人很可能就要付出代价了添加它的成本更高。 找出错误并修复问题。

问:如果我的 C++ 程序想要包含一个 C 头文件啊,其内容包含 C 函数/变量声明,但它们不使用 "C" 链接规范,我该怎么办?

- 答:添加它到啊。

有人可能会建议,如果ah没有“C”而b.cpp包含ah,可以添加:

extern "C"{  #include "a.h"}

这是一个邪恶的计划,原因我们之前已经解释过。 但值得探讨的是,这个解决方案背后可能有一个假设,那就是我们无法修改啊。 无法修改的原因可能来自于两个方面:

1、头文件代码属于其他团队或第三方公司,您无权修改代码;

2、虽然你有修改代码的权限,但是由于这个头文件属于遗留系统,贸然修改可能会导致不可预知的问题。

第一种情况,不要尝试自己动手,这会给您带来不必要的麻烦。 正确的解决方案是将其视为错误并将缺陷报告发送给适当的团队或第三方公司。

如果您付费的是您自己公司的团队或第三方公司,他们有义务为您进行此类修改。 如果他们不明白这一点的重要性,请告诉他们。 如果这些头文件属于自由开源软件,请自行进行正确的修改,并将补丁发布给其开发团队。

第二种情况,你需要放弃这种不必要的安全意识。

因为,首先,对于大多数头文件来说,这种修改并不是复杂或者高风险的修改,一切都在可控范围之内;

其次,如果某个头文件混乱且复杂,虽然遗留系统的哲学应该是:“在它引起麻烦之前不要碰它”,但既然麻烦已经来了,与其逃避,不如面对它它,所以最好的策略是删除它。 将其视为将事物整理到干净合理状态的好机会。

问:我们代码中 "C"的写法如下。 它是否正确?

c语言头文件的作用_c语音头文件_c.语言头文件

答:不确定。

根据C++规范, 的值应该定义为,它是一个非零值; 尽管有些编译器没有按照规范实现它,但它们仍然可以保证该值不为零——至少我到目前为止还没有看到哪个编译器将其实现为0。

在这种情况下,#if ... #endif 是完全多余的。

然而,C++编译器厂商众多,没有人能保证某个编译器,或者某个编译器的早期版本,不会将0的值定义为0。

但即便如此,只要保证该宏只在C++编译器中预定义,那么仅仅使用#ifdef⋯#endif就足以保证意图的正确性; 额外使用 #if ... #endif 是错误的。

仅在这种情况下:即预定义了某厂家的C语言和C++语言编译器,但通过其值为0和非零来区分,使用#if ... #endif是正确且必要的的。

由于现实世界如此复杂,你需要明确自己的目标,然后根据你的目标制定相应的策略。 例如:如果你的目标是让你的代码能够使用正确符合规范的几个主流编译器进行编译,那么你只需要简单地使用#ifdef ... #endif。

但如果你的产品是一个雄心勃勃的、跨平台的产品,试图兼容各种编译器(包括未知的编译器),我们可能不得不使用下面的方法来处理各种情况,那就是识别C和C++中的那些。编译器在编译期间定义宏。

c.语言头文件_c语音头文件_c语言头文件的作用

这样应该可以,但是在每个头文件中写这么长的列表不仅不美观,而且会导致策略一旦修改就会到处修改的情况。 违反 DRY(不要)原则,您将始终需要为此付出额外的代价。 解决这个问题的一个简单解决方案是定义一个特定的头文件 - 例如 .h,并在其中添加以下定义:

c语音头文件_c.语言头文件_c语言头文件的作用

在以下示例中,c 函数声明和定义分别位于 cfun.h 和 cfun.c 中。 该函数打印字符串“this is c fun call”。 C++ 函数声明和定义分别位于 .h 和 .cpp 中。 该函数打印字符串。 “这是 cpp fun call”,编译环境

c++调用c方法(关键是让c函数以c方式编译,而不是c++方式)

(1)cfun.h如下:

#ifndef _C_FUN_H_#define _C_FUN_H_
void cfun();
#endif

.cpp如下:

//#include "cfun.h"  不需要包含cfun.h#include "cppfun.h"#include using namespace std;extern "C"     void cfun(); //声明为 extern void cfun(); 错误
void cppfun(){ cout<<"this is cpp fun call"<<endl;}
int main(){ cfun(); return 0;}

(2)cfun.h 同上

.cpp如下:

extern "C"{    #include "cfun.h"//注意include语句一定要单独占一行;}#include "cppfun.h"#include using namespace std;
void cppfun(){ cout<<"this is cpp fun call"<<endl;}
int main(){ cfun(); return 0;}

(3)cfun.h如下:

#ifndef _C_FUN_H_#define _C_FUN_H_
#ifdef __cplusplusextern "C"{#endif
void cfun();
#ifdef __cplusplus}#endif
#endif

.cpp如下:

#include "cfun.h"#include "cppfun.h"#include using namespace std;
void cppfun(){ cout<<"this is cpp fun call"<<endl;}
int main(){ cfun(); return 0;}

c调用c++(关键是C++提供了符合C调用约定的函数)

上述测试时,我没有声明任何东西,只是在cfun.c中包含.h,然后调用()编译运行。 但是在gcc下出现编译错误。 根据c++/c标准,这种做法应该是错误的。 .以下方法适用于两种编译器

.h如下:

#ifndef _CPP_FUN_H_#define _CPP_FUN_H_
extern "C" void cppfun();

#endif

cfun.c如下:

//#include "cppfun.h" //不要包含头文件,否则编译出错#include "cfun.h"#include 
void cfun(){ printf("this is c fun call\n");}
extern void cppfun();
int main(){#ifdef __cplusplus cfun();#endif cppfun(); return 0;}

注意

由于微信公众号近期改变了推送规则,为了防止找不到,可以星标置顶,这样每次推送的文章才会出现在您的订阅列表里。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注下方公众号,免费领取300G编程资料。

c语言头文件的作用_c.语言头文件_c语音头文件

标签: 编译程序 extern

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码