extern "C"
在C++代码中的作用是什么?
例如:
extern "C" {
void foo();
}
extern "C"
在C++代码中的作用是什么?
例如:
extern "C" {
void foo();
}
extern "C"
使得 C++ 中的函数名具有 C 语言链接(编译器不会对名称进行重整),因此客户端 C 代码可以使用一个仅包含您函数声明的 C 兼容头文件来链接(使用)您的函数。您的函数定义包含在二进制格式中(由您的 C ++ 编译器编译),客户端 C 链接器将使用该 C 名称进行链接。
由于 C++ 具有函数名重载功能,而 C 不支持,因此 C++ 编译器无法仅使用函数名作为唯一 ID 进行链接,因此它通过添加关于参数的信息对名称进行重整。C 编译器不需要重整名称,因为在 C 中不能重载函数名。当您在 C++ 中声明具有 extern "C"
链接时,C++ 编译器不会向用于链接的名称添加参数/参数类型信息。
只需知道,您可以明确指定每个声明/定义的 extern "C"
链接,也可以使用一个块将一系列声明/定义分组以具有特定的链接:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
如果你关心技术细节,它们在C++03标准的第7.5节中列出,这里是一个简要概述(重点在于extern "C"
):
extern "C"
是一种链接说明extern "C"
对类成员无效extern "C"
强制函数具有外部链接(不能将其设置为static)extern "C"
中的static
是有效的。这样声明的实体具有内部链接,因此没有语言链接extern "C" { int i; }
是一个定义。这可能不是您的意图,因为void g(char);
是非定义的。要使其成为非定义,您需要使用extern "C" { extern int i; }
。另一方面,省略括号的单声明语法确实使声明成为非定义:extern "C" int i;
与 extern "C" { extern int i; }
相同。 - aschepler我想补充一些信息,因为我还没有看到它被发布过。
你经常会在C头文件中看到如下的代码:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
这样做的好处是可以让你在C++代码中使用那个C头文件,因为宏__cplusplus
会被定义。但你也可以在遗留的C代码中使用它,因为宏没有被定义,所以它将不会看到独有的C++结构。
虽然我也见过像这样的C++代码:
extern "C" {
#include "legacy_C_header.h"
}
我想这样做会达到同样的效果。
不确定哪种方法更好,但我都见过。
extern "C"
。这种技术非常好用,我已经用过很多次了。 - Ben Voigtextern "C"
都不会有所区别。在它传送给编译器之前,已经被预处理成了一个长字符串。 - Ben Voigtg++
会在任何目标上出现这种错误。第一个示例的整个重点在于,无论您使用C还是C ++编译器,都不会对extern“C”
块中的名称进行名称修饰。 - Jonathan Wakelyg++
生成的二进制文件以查看其运行情况。
主文件:main.cppvoid f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
编译并反汇编生成的ELF输出:
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
输出内容包括:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
解释
我们可以发现:
ef
和eg
被存储在与代码中同名的符号中。
其他符号已被混淆。让我们对它们进行反混淆:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
结论:以下两种符号类型都没有被重整:
Ndx = UND
),需要在链接或运行时从另一个目标文件提供因此,在以下情况下,您需要使用extern "C"
:
g++
期望由gcc
生成的未经重整的符号g++
生成gcc
要使用的未经重整的符号在extern C中无法工作的内容
很明显,任何需要命名重整的C++特性都不能在extern C
内部工作:
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++项目中使用C源文件?
从C++中调用C很容易:每个C函数只有一个可能的非重载符号,因此不需要额外的工作。
main.cpp
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
c.h
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++
* because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
c.c
#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++
要找到一个被改名过的函数 f
,而 gcc
没有生成这样的函数。C 语言最小可运行 C++ 示例代码
从 C 中调用 C++ 有点困难:我们必须手动创建每个要公开的函数的非改名版本。
这里我们演示如何将 C++ 函数重载暴露给 C。
main.c
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
cpp.h
#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
cpp.cpp
#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'
因为 g++
生成的符号被 gcc
找不到。
C 头文件在 C++ 中被包含时 extern "c"
在哪里?
cstdio
)可能依赖于 #pragma GCC system_header
,这个链接:https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html 描述道:“对于一些目标平台(例如 RS/6000 AIX),GCC 在以 C++ 编译时隐式地用 'extern "C"' 块围绕所有系统头文件。”,但我没有完全确认过。/usr/include/unistd.h
)通过 __BEGIN_DECLS
在 Ubuntu 20.04 上被覆盖,具体内容可以查看这个链接:Do I need an extern "C" block to include standard POSIX C headers?。这个宏通过 #include <features.h>
包含。在 Ubuntu 18.04 上测试通过。
extern "C" {
可以帮助您在C++程序中调用未被mangled的C函数,并且在C程序中调用未被mangled的C ++函数,而其他答案没有这么明显,同时您还给出了每种情况的不同示例,因此这是最佳答案。谢谢! - Gabriel Staples__BEGIN_DECLS
启用了extern C:https://dev59.com/questions/HWsz5IYBdhLWcg3wJUjJ#8087539 我在Ubuntu 20.04上观察到了那个答案中提到的内容,适用于unistd.h。然而,对于cstdio
,它可能依赖于#pragma GCC system_header
:https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html - Ciro Santilli OurBigBook.com#include <features.h>
包含进来。 - Ciro Santilli OurBigBook.com大多数编程语言并非在现有编程语言之上构建而成。C++是在C语言之上构建的,而且它是一种从过程式编程语言发展而来的面向对象编程语言,因此有一些C++表达式(例如extern "C"
)提供与C语言的反向兼容性。
让我们看下面这个例子:
#include <stdio.h>
// Two functions are defined with the same name
// but have different parameters
void printMe(int a) {
printf("int: %i\n", a);
}
void printMe(char a) {
printf("char: %c\n", a);
}
int main() {
printMe('a');
printMe(1);
return 0;
}
因为上述示例中定义了相同名称的函数printMe
,虽然它们有不同的参数int a
和char a
,所以C编译器将无法编译。
gcc -o printMe printMe.c && ./printMe;
1 error. PrintMe is defined more than once.
但是,C++编译器可以编译上述示例。它并不在乎printMe
被定义了两次。
g++ -o printMe printMe.c && ./printMe;
这是因为C++编译器会根据参数名隐式地为函数进行重命名(mangles)。该语言旨在创建具有相同名称的方法(函数)的不同类,并基于不同的参数来覆盖方法名(method overriding)。
extern "C"
表示“不要重命名C函数名”即使C++是基于C构建的,混淆对于C代码可能会造成麻烦。例如,假设我们有一个名为“parent.c”的遗留C文件,该文件include
来自不同头文件“parent.h”、“child.h”等的函数名称。如果我们将“parent.c”通过C++编译器运行,它将混淆该文件中的函数名,并且它们将不再与头文件中指定的函数名匹配。因此,“parent.h”和“child.h”头文件中的函数名也需要进行混淆。对于少数文件,这可能没问题,但如果C程序很复杂,混淆可能会导致代码错误和执行速度变慢,因此提供一个关键字告诉C++编译器不要混淆函数名可能是方便的。
extern "C"
关键字告诉C++编译器不要混淆(重命名)C函数名。
例如:
extern "C" void printMe(int a);
dll
文件,没有头文件,只有源文件(仅实现),并且通过函数指针使用其函数,那么我们可以不使用extern "C"
吗?在这种情况下,我们只是使用函数(而不考虑其名称)。 - BattleTested_закалённый в бою仅仅通过用extern "C"来包装C头文件并不能使所有的C头文件与C++兼容。当C头文件中的标识符与C++关键字冲突时,C++编译器将会报错。
例如,我曾在g++编译器下看到以下代码无法编译通过:
extern "C" {
struct method {
int virtual;
};
}
有点合理,但在将C代码移植到C++时要记住这一点。
extern "C"
表示使用 C 语言链接,正如其他回答所述。它并不意味着将内容编译为 C 语言或其他任何内容。在 C++ 中,int virtual;
是无效的,并且指定不同的链接方式也无法改变这一点。 - M.Mextern "C"{...}
包装器之外,现在它可以在C和C++版本上编译。 - penguin359它以这样一种方式改变函数的链接方式,使得该函数可以从C中调用。实际上,这意味着函数名没有名称混淆。
该指令告知C++编译器在链接时以C语言风格查找这些函数的名称,因为在链接阶段,用C和C++编译的函数的名称是不同的。
extern "C"
的作用是告诉 C++ 编译器该函数是以 C 语言风格编写的(或将会被编写成这种风格),以便在链接时正确地链接到 C 语言版本的函数。
extern "C"
是一种链接规范,用于在 C++ 源文件中调用 C 函数。我们可以调用 C 函数、编写变量和包含头文件。函数在 extern 实体中声明,但其定义在外部。语法如下:
类型 1:
extern "language" function-prototype
类型 2:
extern "language"
{
function-prototype
};
例如:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}