放入 C++ 代码到底有什么作用extern "C"
?
例如:
extern "C" {
void foo();
}
extern "C"
使 C++ 中的函数名具有 C 链接(编译器不会破坏名称),以便客户端 C 代码可以使用仅包含函数声明的 C 兼容头文件链接到(使用)您的函数。您的函数定义包含在二进制格式(由您的 C++ 编译器编译)中,然后客户端 C 链接器将使用 C 名称链接到该格式。
由于 C++ 有函数名重载,而 C 没有,C++ 编译器不能只使用函数名作为链接的唯一 id,因此它通过添加有关参数的信息来破坏名称。AC 编译器不需要修改名称,因为您不能在 C 中重载函数名称。当您声明函数extern "C"
在 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"
只是想添加一些信息,因为我还没有看到它发布。
你会经常在 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"
}
我想这可以完成同样的事情。
不知道哪种方式更好,但我都见过。
反编译g++
生成的二进制文件以查看发生了什么
主文件
void 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
供使用在外部 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 非常简单:每个 C 函数只有一个可能的未损坏符号,因此不需要额外的工作。
主文件
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
ch
#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
抄送
#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++
期望找到一个 mangled f
,它gcc
没有产生。
C 示例中的最小可运行 C++
从 C 调用 C++ 有点困难:我们必须手动创建要公开的每个函数的非损坏版本。
在这里,我们说明了如何将 C++ 函数重载暴露给 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
extern "c"
当我包含来自 C++ 的 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
包含在:Do I need an extern "C" block to include standard POSIX C headers? 通过__BEGIN_DECLS
,在 Ubuntu 20.04 上转载。__BEGIN_DECLS
包含在#include <features.h>
.在 Ubuntu 18.04 中测试。
在每个 C++ 程序中,所有非静态函数在二进制文件中都表示为符号。这些符号是在程序中唯一标识一个函数的特殊文本字符串。
在 C 中,符号名称与函数名称相同。这是可能的,因为在 C 中没有两个非静态函数可以具有相同的名称。
因为 C++ 允许重载并且有许多 C 不具备的特性——比如类、成员函数、异常规范——不可能简单地使用函数名作为符号名。为了解决这个问题,C++ 使用了所谓的名称修饰,它将函数名称和所有必要的信息(如参数的数量和大小)转换为一些看起来很奇怪的字符串,只能由编译器和链接器处理。
因此,如果您将函数指定为 extern C,编译器不会对其执行名称修改,并且可以使用其符号名称作为函数名称直接访问它。
这在使用dlsym()
和dlopen()
调用此类函数时很方便。
大多数编程语言都不是建立在现有编程语言之上的。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;
}
AC 编译器不会编译上面的例子,因为同一个函数printMe
被定义了两次(即使它们有不同的参数int a
vs char a
)。
gcc -o printMe printMe.c && ./printMe;
1 个错误。PrintMe 被多次定义。
C++ 编译器将编译上述示例。它不关心printMe
定义了两次。
g++ -o printMe printMe.c && ./printMe;
这是因为 C++ 编译器会根据参数隐式重命名 ( mangles ) 函数。在 C 中,不支持此功能。但是,当 C++ 在 C 之上构建时,该语言被设计为面向对象的,需要支持创建具有相同名称的方法(函数)的不同类的能力,以及基于不同的方法覆盖方法(方法覆盖)的能力。参数。
extern "C"
说“不要破坏 C 函数名称”但是,假设我们有一个名为“parent.c”的遗留 C 文件,它include
是来自其他遗留 C 文件的函数名称,“parent.h”、“child.h”等。如果运行遗留的“parent.c”文件通过 C++ 编译器,函数名将被破坏,它们将不再与“parent.h”、“child.h”等中指定的函数名匹配 - 所以这些外部文件中的函数名也需要被破坏。在复杂的 C 程序中修改函数名称,这些程序有很多依赖项,可能会导致代码损坏;因此提供一个可以告诉 C++ 编译器不要破坏函数名称的关键字可能会很方便。
该extern "C"
关键字告诉 C++ 编译器不要破坏(重命名)C 函数名称。
例如:
extern "C" void printMe(int a);
仅通过包装在 extern "C" 中,不能使任何 C 头文件与 C++ 兼容。当 C-header 中的标识符与 C++ 关键字冲突时,C++ 编译器会抱怨这一点。
例如,我在 g++ 中看到以下代码失败:
extern "C" {
struct method {
int virtual;
};
}
有点道理,但是在将 C 代码移植到 C++ 时要牢记这一点。
它改变了函数的链接,使得函数可以从 C 中调用。在实践中,这意味着函数名没有被破坏。
它通知 C++ 编译器在链接时以 C 风格查找这些函数的名称,因为在链接阶段用 C 和 C++ 编译的函数的名称是不同的。
extern "C"
旨在被 C++ 编译器识别并通知编译器指出的函数已(或将)以 C 样式编译,以便在链接时,它链接到 C 函数的正确版本。
extern "C"
是一种链接规范,用于调用Cpp 源文件中的C 函数。我们可以调用 C 函数、编写变量和包含头文件。函数在外部实体中声明,并且在外部定义。语法是
类型 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)
{
//
//
}
我之前对 dll(动态链接库)文件使用了 'extern "C"' 来使等 main() 函数“可导出”,因此以后可以在 dll 的另一个可执行文件中使用它。也许我曾经使用它的一个例子很有用。
动态链接库
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE文件
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
这个答案是为急躁/有截止日期的人准备的,下面只有一部分/简单的解释:
所以
在 C++ 中,在 C 中使用名称修饰唯一标识每个函数
,即使没有名称修饰唯一标识每个函数
要更改 C++ 的行为,即指定不应为特定函数发生名称修改,无论出于何种原因,您都可以在函数名称前使用extern "C",例如从 dll 导出具有特定名称的函数,供其客户使用。
阅读其他答案,以获得更详细/更正确的答案。
C编译器编译的函数void f()和C++编译器编译的同名函数void f()不是同一个函数。如果您在 C 中编写了该函数,然后您尝试从 C++ 调用它,那么链接器将查找 C++ 函数但找不到 C 函数。
extern "C" 告诉 C++ 编译器你有一个由 C 编译器编译的函数。一旦你告诉它它是由 C 编译器编译的,C++ 编译器就会知道如何正确调用它。
它还允许 C++ 编译器以 C 编译器可以调用它的方式编译 C++ 函数。该函数将正式成为 C 函数,但由于它是由 C++ 编译器编译的,因此它可以使用所有 C++ 功能并具有所有 C++ 关键字。
当混合 C 和 C++ 时(即 a. 从 C++ 调用 C 函数;和 b. 从 C 调用 C++ 函数),C++ 名称修改会导致链接问题。从技术上讲,这个问题只有在被调用函数已经使用相应的编译器编译成二进制文件(很可能是 *.a 库文件)时才会发生。
所以我们需要使用 extern "C" 来禁用 C++ 中的名称修饰。
在不与其他好的答案冲突的情况下,我将添加一些我的示例。
C++ 编译器究竟做了什么:它在编译过程中破坏了名称,因此我们需要告诉编译器特别对待 C
实现。
当我们创建 C++ 类并添加extern "C"
时,我们是在告诉 C++ 编译器我们正在使用 C 调用约定。
原因(我们从 C++ 调用 C 实现): 要么我们想从 C++ 调用 C 函数,要么从 C 调用 C++ 函数(C++ 类......等在 C 中不起作用)。
请参阅下面的链接,这是 Geeks for Geeks 对 extern "C" 用法的解释。从下面的页面添加重要信息。
考虑以下函数 f() 的声明
int f (void) { return 1; }
int f (int) { return 0; }
void g (void) { int i = f(), j = f(0); }
C++ 编译器可能会将上述名称更改为以下名称(来源:Wiki)
int __f_v (void) { return 1; }
int __f_i (int) { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }