2008-09-15 59 views
117

为什么我们需要使用:为什么我们需要在C++中使用extern“C”{#include <foo.h>}?

extern "C" { 
#include <foo.h> 
} 

具体做法是:

  • 我们什么时候应该使用它?

  • 在编译器/链接器级别发生什么事情需要我们使用它?

  • 在编译/链接方面,这是否解决了需要我们使用它的问题?

+0

我很困惑你的问题题目是什么意思......你能详细说明一下吗? – 2008-09-15 23:21:27

+29

我不知道该怎么说。你读过标题以外的内容吗? – Landon 2008-09-15 23:27:58

回答

106

C和C++表面上很相似,但每个编译成一组非常不同的代码。当您使用C++编译器包含头文件时,编译器需要C++代码。但是,如果它是一个C头文件,那么编译器希望包含在头文件中的数据被编译为某种格式 - C++“ABI”或“应用程序二进制接口”,这样链接器就会窒息。这比将C++数据传递给期望C数据的函数更可取。

(要进入真正的事实真相,C++的ABI一般‘轧液’它们的功能/方法的名称,所以调用printf()毫不气馁的原型C函数的C++实际上会产生代码调用_Zprintf,加)

因此:使用extern "C" {...};包括交流标题 - 这很简单。否则,你在编译代码时会出现不匹配,链接器会窒息。然而,对于大多数头文件,您甚至不需要extern,因为大多数系统C头文件已经说明了它们可能包含在C++代码中并且已经代码为extern

+1

请您详细说明**“大多数系统的C头文件已经说明了这样一个事实,即它们可能被C++代码包含,并且已经存在它们的代码。”**? – 2016-09-28 18:39:52

13

它与不同的编译器执行名称修改的方式有关。 C++编译器将以与C编译器完全不同的方式来压缩从头文件导出的符号的名称,因此,当您尝试链接时,会出现链接器错误,指出缺少符号。

为了解决这个问题,我们告诉C++编译器以“C”模式运行,所以它会以与C编译器相同的方式执行名称修改。完成后,链接器错误得到修复。

5

这是用来解决名称修改问题。 extern C意味着这些函数处于“扁平”C风格的API中。

6

C++编译器创建的符号名称与C编译器不同。因此,如果您试图调用驻留在C文件中的函数(编译为C代码),则需要告诉C++编译器它正在尝试解析的符号名称与默认的符号名称不同;否则链接步骤将失败。

18

在C++中,可以有不同的实体共享一个名称。例如,下面是功能列表中的所有命名

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

为了所有这些,C++编译器之间的区别将在称为名称 - 装饰或装饰的过程中为每个名称创建独特的名称。 C编译器不这样做。而且,每个C++编译器都可以做到这一点,但方式不同。

extern“C”告诉C++编译器不要对花括号内的代码执行任何名称修改。这允许你从C++中调用C函数。

10

C和C++对符号的名称有不同的规则。符号是链接器如何知道编译器生成的一个目标文件中的函数“openBankAccount”的调用是对另一个由不同源文件通过相同(或兼容)文件生成的另一个目标文件中称为“openBankAccount”的函数的引用。编译器。这使您可以从多个源文件中创建程序,这对于处理大型项目是一种解脱。

在C中,规则非常简单,无论如何,符号都在单个名称空间中。所以整数“socks”存储为“socks”,而函数count_socks存储为“count_socks”。

链接器是为C语言和C语言等其他语言构建的,具有这种简单的符号命名规则。所以链接器中的符号只是简单的字符串。

但是在C++中,语言让你拥有命名空间,多态以及与这样一个简单规则相冲突的各种其他事物。所有称为“add”的六个多态函数都需要具有不同的符号,否则错误的函数将被其他目标文件使用。这是通过“捣毁”(这是一个技术术语)符号的名称来完成的。当将C++代码链接到C库或代码时,需要使用C语言编写的任何东西(例如C库的头文件)的外部“C”来告诉C++编译器这些符号名称不会被损坏,而其余的C++代码当然必须被破坏或不起作用。

10

我们什么时候应该使用它?

当你连接C libaries到C++对象文件

什么是在要求我们 使用它 编译器/连接水平发生了什么?

C和C++使用不同的符号命名方案。这告诉链接器在给定库中链接时使用C的方案。

如何在编制方面/联 这是否解决了这 要求我们使用它的问题?

使用C命名方案可以引用C样式的符号。否则,链接器会尝试不起作用的C++风格的符号。

5

extern "C" {}构造函数指示编译器不要对花括号中声明的名称执行修改。通常,C++编译器会“增强”函数名称,以便对参数和返回值的类型信息进行编码;这被称为损坏的名称extern "C"构造可防止损坏。

它通常在C++代码需要调用C语言库时使用。它也可以在将C++函数(例如,从DLL)公开给C客户端时使用。

104

extern“C”确定应如何命名生成的对象文件中的符号。如果一个函数声明为不带外部“C”,那么目标文件中的符号名将使用C++名称修饰。这是一个例子。

鉴于TEST.C像这样:

void foo() { } 

编译并在目标文件中列出符号给出:

$ g++ -c test.C 
$ nm test.o 
0000000000000000 T _Z3foov 
       U __gxx_personality_v0 

foo的功能实际上是所谓的 “_Z3foov”。该字符串包含返回类型和参数的类型信息等等。如果你不是写TEST.C这样的:

extern "C" { 
    void foo() { } 
} 

然后编译看看符号:

$ g++ -c test.C 
$ nm test.o 
       U __gxx_personality_v0 
0000000000000000 T foo 

你得到C链接。对象文件中的“foo”函数的名称只是“foo”,并没有来自名称修饰的所有花式类型信息。

如果与它一起编译的代码是用C编译器编译的,但您试图从C++调用它,那么通常在extern“C”{}中包含一个头文件。当你这样做时,你告诉编译器头中的所有声明都将使用C链接。当你链接你的代码时,你的.o文件将包含对“foo”的引用,而不是“_Z3fooblah”,它有望匹配你连接的库中的任何内容。

大多数现代化的图书馆都会在这些标题周围放置警卫,以便符号可以通过正确的链接进行声明。例如在很多标准的头,你会发现:

#ifdef __cplusplus 
extern "C" { 
#endif 

... declarations ... 

#ifdef __cplusplus 
} 
#endif 

这可以确保当C++代码包括头,在你的目标文件匹配什么是在C库中的符号。你只需要在你的C头部附近放置extern“C”{},如果它是旧的,并且已经没有这些防护。

6

只要包含一个头文件,就可以随时使用extern“C”,该头文件定义驻留在由C编译器编译的文件中的函数,用于C++文件。 (许多标准的C库可能会在其标题中包含此检查以便开发人员更简单)

例如,如果您有一个包含3个文件的项目,util.c,util.h和main.cpp以及两者.c和.cpp文件用C++编译器(g ++,cc等)编译,那么它不是真的需要,甚至可能导致链接器错误。如果您的构建过程对util.c使用常规C编译器,那么在包含util.h时将需要使用extern“C”。

发生了什么事情是C++在其名称中编码函数的参数。这是函数重载的工作原理。所有倾向于发生在C函数中的是在名称的开头添加下划线(“_”)。如果不使用extern“C”,当函数的实际名称是_DoSomething()或者仅仅是DoSomething()时,链接器将寻找名为DoSomething @@ int @ float()的函数。

使用extern“C”通过告诉C++编译器它应该查找一个遵循C命名约定而不是C++的函数来解决上述问题。

相关问题