为什么我们需要使用:
extern "C" {
#include <foo.h>
}
具体来说:
我们什么时候应该使用它?
编译器/链接器级别发生了什么,需要我们使用它?
如何在编译/链接方面解决需要我们使用它的问题?
答案 0 :(得分:116)
C和C ++在表面上相似,但每个编译成一组非常不同的代码。当您使用C ++编译器包含头文件时,编译器需要C ++代码。但是,如果它是一个C头,则编译器需要将头文件中包含的数据编译为某种格式--C ++'ABI'或'应用程序二进制接口',因此链接器会扼杀。这比将C ++数据传递给期望C数据的函数更好。
(为了深入了解真正的细节,C ++的ABI通常会“破坏”其函数/方法的名称,因此调用printf()
而不将原型标记为C函数,C ++实际上会生成代码调用_Zprintf
,加上最后的额外垃圾。)
所以:在包含c标题时使用extern "C" {...}
- 就这么简单。否则,您将在编译的代码中出现不匹配,并且链接器将会阻塞。但是,对于大多数标头,您甚至不需要extern
,因为大多数系统C标头已经考虑到它们可能包含在C ++代码中并且已经extern
代码的事实。
答案 1 :(得分:107)
extern“C”确定应如何命名生成的目标文件中的符号。如果声明的函数没有extern“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标题周围放置外部“C”{},如果它已经老了并且没有这些警卫。
答案 2 :(得分:21)
在C ++中,您可以拥有共享名称的不同实体。例如,这里是一个名为 foo 的函数列表:
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
为了区分它们,C ++编译器将在名为name-mangling或decorating的过程中为每个创建唯一的名称。 C编译器不这样做。此外,每个C ++编译器可能会采用不同的方式。
extern“C”告诉C ++编译器不要对大括号内的代码执行任何名称修改。这允许您从C ++中调用C函数。
答案 3 :(得分:14)
它与不同编译器执行名称修改的方式有关。 C ++编译器将以与C编译器完全不同的方式破坏从头文件导出的符号的名称,因此当您尝试链接时,您将收到链接器错误,指出缺少符号。
要解决此问题,我们告诉C ++编译器以“C”模式运行,因此它以与C编译器相同的方式执行名称修改。完成后,链接器错误已得到修复。
答案 4 :(得分:11)
我们什么时候应该使用它?
将C库链接到C ++目标文件时
发生了什么事 需要我们的编译器/链接器级别 使用它?
C和C ++使用不同的符号命名方案。这告诉链接器在给定库中链接时使用C的方案。
如何编译/链接 这是否解决了这些问题 要求我们使用它吗?
使用C命名方案可以引用C风格的符号。否则,链接器将尝试C ++样式的符号,这些符号不起作用。
答案 5 :(得分:10)
C和C ++对符号名称有不同的规则。符号是链接器如何知道编译器生成的一个目标文件中对“openBankAccount”函数的调用是对在另一个源文件中由同一个(或兼容的)生成的另一个目标文件中称为“openBankAccount”的函数的引用编译器。这允许您使用多个源文件创建程序,这对于处理大型项目来说是一种解脱。
在C中,规则非常简单,无论如何,符号都在一个名称空间中。所以整数“socks”存储为“socks”,函数count_socks存储为“count_socks”。
使用这个简单的符号命名规则,为C和其他语言(如C)构建了连接器。所以链接器中的符号只是简单的字符串。
但是在C ++中,语言允许你拥有名称空间,多态性以及与这样一个简单规则冲突的各种其他东西。所有六个称为“add”的多态函数都需要有不同的符号,否则其他目标文件将使用错误的符号。这是通过“修改”(这是技术术语)符号的名称来完成的。
当将C ++代码链接到C库或代码时,您需要使用C语言编写的外部“C”,例如C库的头文件,以告诉C ++编译器这些符号名称不会被破坏,而当然,其余的C ++代码必须被破坏,否则它将无效。
答案 6 :(得分:7)
C ++编译器创建符号名称的方式与C编译器不同。因此,如果您尝试调用驻留在C文件中的函数(编译为C代码),则需要告诉C ++编译器它尝试解析的符号名称与默认值不同;否则链接步骤将失败。
答案 7 :(得分:7)
如果包含定义函数的头,则应该使用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 ++命令的函数来解决上述问题。
答案 8 :(得分:6)
extern "C" {}
构造指示编译器不对在大括号内声明的名称执行修改。通常,C ++编译器“增强”函数名称,以便它们编码有关参数和返回值的类型信息;这称为受损名称。 extern "C"
构造可以防止重整。
通常在C ++代码需要调用C语言库时使用。它也可以在将C ++函数(例如,从DLL)暴露给C客户端时使用。
答案 9 :(得分:5)
这用于解决名称重整问题。 extern C意味着函数采用“扁平”C风格的API。
答案 10 :(得分:0)
反编译b=$((a+b))
生成的二进制文件以查看发生了什么
我将答案从What is the effect of extern "C" in C++?中移出,因为该问题被认为是该问题的重复。
main.cpp
g++
使用GCC 4.8 Linux ELF输出进行编译:
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
反编译符号表:
g++ -c main.cpp
输出包含:
readelf -s main.o
解释
我们看到了:
Num: Value Size Type Bind Vis Ndx Name
8: 0000000000000000 6 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000006 6 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000c 16 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
和ef
存储在与代码同名的符号中
其他符号被弄乱了。让我们解开它们:
eg
结论:以下两种符号类型均被 not 修饰:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
),将在链接或运行时从另一个目标文件提供因此,致电时您都需要Ndx = UND
>
extern "C"
期望由g++
产生的未修饰符号gcc
生成{m1}的未损坏符号,以供使用在外部C中不起作用的东西
很明显,任何需要名称修饰的C ++功能都无法在g++
内部使用:
gcc
C ++示例中的最小可运行C
出于完整性和新手的原因,另请参见:How to use C source files in a C++ project?
从C ++调用C非常容易:每个C函数只有一个可能的非分解符号,因此不需要额外的工作。
main.cpp
extern C
c.h
extern "C" {
// Overloading.
// error: declaration of C function ‘void f(int)’ conflicts with
void f();
void f(int i);
// Templates.
// error: template with C linkage
template <class C> void f(C i) { }
}
c.c
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
运行:
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
没有#include "c.h"
int f(void) { return 1; }
的链接将失败,并显示以下信息:
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out
因为extern "C"
希望找到main.cpp:6: undefined reference to `f()'
没有产生的损坏的g++
。
C示例中的最小可运行C ++
从中调用C ++有点困难:我们必须手动创建要公开的每个函数的非混合版本。
这里我们说明了如何向C公开C ++函数重载。
main.c
f
cpp.h
gcc
cpp.cpp
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
运行:
#ifndef CPP_H
#define CPP_H
#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif
#endif
如果没有#include "cpp.h"
int f(int i) {
return i + 1;
}
int f(float i) {
return i + 2;
}
int f_int(int i) {
return f(i);
}
int f_float(float i) {
return f(i);
}
,它将失败,并显示以下信息:
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out
因为extern "C"
生成了main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
无法找到的错误的符号。
在Ubuntu 18.04中进行了测试。