为什么在C ++中需要extern" C" {#include}?
我们为什么需要使用:
extern "C" { #include <foo.h> }
具体来说:
- 我们什么时候应该使用它?
- 需要我们使用它的编译器/链接器级别发生了什么?
- 在编译/链接方面,这如何解决需要我们使用它的问题?
解决方案
回答
它与不同的编译器执行名称处理的方式有关。 C ++编译器将以与C编译器完全不同的方式来处理从头文件导出的符号的名称,因此,当我们尝试链接时,会出现链接器错误,指出缺少符号。
为了解决这个问题,我们告诉C ++编译器以" C"模式运行,因此它以与C编译器相同的方式执行名称处理。这样做后,链接器错误已修复。
回答
这用于解决名称处理问题。 extern C表示函数使用"平面" C风格的API。
回答
C ++编译器创建的符号名称与C编译器不同。因此,如果我们试图调用驻留在C文件中并编译为C代码的函数,则需要告诉C ++编译器它要解析的符号名称看起来不同于默认名称。否则,链接步骤将失败。
回答
C和C ++在表面上是相似的,但是每个都编译成非常不同的代码集。当我们在C ++编译器中包含头文件时,编译器将使用C ++代码。但是,如果它是C头,则编译器希望将头文件中包含的数据编译为C ++'ABI'或者'Application Binary Interface'的某种格式,因此链接器阻塞。这比将C ++数据传递给期望C数据的函数更好。
(要想真正了解细节,C ++的ABI通常会"破坏"其函数/方法的名称,因此调用printf()
而不将原型标记为C函数,C ++实际上会生成调用_Zprintf
的代码,最后加上多余的废话。)
因此:在包含c标头时使用extern" C" {...};
就这么简单。否则,编译后的代码将不匹配,链接器将阻塞。但是,对于大多数头文件,我们甚至都不需要extern
,因为大多数系统C头文件已经考虑到了它们可能被C ++代码包含并且已经extern
代码的事实。
回答
在C ++中,可以有不同的实体共享一个名称。例如,下面是所有名为foo的函数的列表:
- A :: foo()
- B :: foo()
- C :: foo(int)`
- C :: foo(std :: string)`
为了区分它们,C ++编译器将在称为名称处理或者修饰的过程中为每个名称创建唯一的名称。 C编译器不这样做。此外,每个C ++编译器可能以不同的方式执行此操作。
extern" C"告诉C ++编译器不要对花括号内的代码执行任何名称处理。这使我们可以从C ++中调用C函数。
回答
C和C ++关于符号名称的规则不同。符号是链接器如何知道对由编译器生成的一个目标文件中的函数" openBankAccount"的调用是对在相同(或者兼容)不同源文件中生成的另一目标文件中称为" openBankAccount"的函数的引用编译器。这使我们可以使用多个源文件制作一个程序,这在处理大型项目时很轻松。
在C语言中,规则非常简单,符号始终都在单个名称空间中。因此,整数" socks"存储为" socks",而函数count_socks存储为" count_socks"。
使用此简单的符号命名规则为C和其他语言(如C)构建了链接器。因此,链接器中的符号只是简单的字符串。
但是在C ++中,该语言允许我们拥有名称空间,多态性以及与这种简单规则相冲突的其他各种功能。我们称为" add"的所有六个多态函数都必须具有不同的符号,否则其他目标文件将使用错误的符号。这是通过"破坏"(这是一个技术术语)符号名称来完成的。
将C ++代码链接到C库或者代码时,我们需要用C编写的任何东西都加上" C"符号(例如C库的头文件),以告诉C ++编译器不要弄乱这些符号名,而其余的当然,C ++代码必须经过修改,否则将无法正常工作。
回答
When should we use it?
当我们将C库链接到C ++目标文件中时
What is happening at the compiler/linker level that requires us to use it?
C和C ++使用不同的方案进行符号命名。这告诉链接器在给定库中链接时使用C的方案。
How in terms of compilation/linking does this solve the problems which require us to use it?
使用C命名方案可以引用C样式符号。否则,链接器将尝试不起作用的C ++样式符号。
回答
" extern" C" {}构造指示编译器不要对花括号内声明的名称进行修饰。通常,C ++编译器会"增强"函数名称,以便它们对有关参数和返回值的类型信息进行编码。这称为错误的名称。
extern" C"`构造防止乱码。
通常在C ++代码需要调用C语言库时使用。将C ++函数(例如,从DLL)公开给C客户端时,也可以使用它。
回答
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编译器编译的,则通常将其包含在extern" C" {}中,但我们尝试从C ++调用它。当我们这样做时,我们是在告诉编译器头文件中的所有声明都将使用C链接。链接代码时,.o文件将包含对" foo"的引用,而不是对" _Z3fooblah"的引用,这些引用有望与要链接的库中的内容匹配。
大多数现代库都会在此类标头周围加保护,以便使用正确的链接声明符号。例如在许多标准标头中,我们会发现:
#ifdef __cplusplus extern "C" { #endif ... declarations ... #ifdef __cplusplus } #endif
这样可以确保当C ++代码包含头文件时,目标文件中的符号与C库中的符号匹配。如果C标头很旧并且还没有这些防护,则只需要在ex标头周围加上extern" C" {}即可。
回答
只要在包含C ++文件中使用的定义由C编译器编译的文件中驻留的函数的标头的任何时候,就应使用extern" C"。 (许多标准C库可能在其标头中包含此检查项,以使其对开发人员更简单)
例如,如果我们有一个包含3个文件util.c,util.h和main.cpp的项目,并且.c和.cpp文件都使用C ++编译器(g ++,cc等)进行了编译,则它不是确实需要,甚至可能导致链接器错误。如果构建过程对util.c使用常规的C编译器,那么在包含util.h时将需要使用extern" C"。
发生的事情是C ++以其名称对函数的参数进行编码。这就是函数重载的工作方式。 C函数易于发生的事情是在名称的开头加上下划线(_)。如果函数的实际名称是_DoSomething()或者仅是DoSomething(),则在不使用外部" C"的情况下,链接器将查找名为DoSomething @@ int @ float()的函数。
使用extern" C"通过告诉C ++编译器它应寻找遵循C命名约定而不是C ++命名的函数来解决上述问题。