外部“C”在C++中的作用是什么?

extern "C"放入C++代码中究竟有什么作用?

例如:

extern "C" {void foo();}
1090154 次浏览

它通知C++编译器在链接时以C风格查找这些函数的名称,因为在链接阶段,用C和C++编译的函数的名称是不同的。

它改变了函数的链接,使函数可以从C中调用。在实践中,这意味着函数名称不是破坏

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"是一个链接规范
  • 每个编译器都是选填/必填来提供“C”链接
  • 链接规范只能出现在命名空间范围内
  • 所有函数类型、函数名和变量名都有语言链接 查看Richard的评论:只有具有外部链接的函数名和变量名具有语言链接
  • 具有不同语言联系的两个函数类型是不同的类型,即使在其他方面相同
  • 链接规格嵌套,内部规格决定最终链接
  • extern "C"被类成员忽略
  • 最多一个具有特定名称的函数可以具有“C”链接(无论命名空间如何)
  • extern "C"中的#0强制函数具有外部链接(不能使其为静态) 见Richard的评论: static是有效的;如此声明的实体具有内部链接,因此没有语言链接
  • 从C++到其他语言定义的对象和从其他语言到C++定义的对象的链接是实现定义的和语言相关的只有在两种语言实现的对象布局策略足够相似的情况下才能实现这种链接

在每个C++程序中,所有非静态函数都在二进制文件中表示为符号。这些符号是唯一标识程序中函数的特殊文本字符串。

在C中,符号名称与函数名称相同。这是可能的,因为在C中,没有两个非静态函数可以具有相同的名称。

因为C++允许重载,并且有许多C语言没有的特性——比如类、成员函数、异常规范——所以不可能简单地使用函数名作为符号名。为了解决这个问题,C++使用所谓的名称修饰,它将函数名和所有必要的信息(比如参数的数量和大小)转换成一些看起来奇怪的字符串,只有编译器和链接器才能处理。

因此,如果您将函数指定为extern C,编译器不会使用它执行名称修饰,它可以直接使用其符号名作为函数名访问。

这在使用dlsym()dlopen()调用此类函数时很方便。

extern "C"旨在被C++编译器识别,并通知编译器注意到的函数是(或将)以C风格编译的,因此在链接时,它链接到C中函数的正确版本。

只是想添加一些信息,因为我还没有看到它发布。

你经常会在C头文件中看到这样的代码:

#ifdef __cplusplusextern "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"}

我想也能达到同样的效果。

我不知道哪一种方式更好,但我都见过。

不是任何C头文件都可以通过仅仅包装在extern“C”中来与C++兼容。当C头文件中的标识符与C++关键字冲突时,C++编译器会抱怨这一点。

例如,我看到以下代码在g++中失败:

extern "C" {struct method {int virtual;};}

有点道理,但在将C代码移植到C++时要记住这一点。

我之前在dll(动态链接库)文件中使用了'extern"C"来使等main()函数“可导出”,以便稍后可以在dll的另一个可执行文件中使用。也许我曾经使用它的示例可能有用。

DLL

#include <string.h>#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)//I defined DLL for dllexport functionDLL 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 dllFunction mainDLLFunc;//make a variable for function placeholder
int main(){char winDir[MAX_PATH];//will hold path of above dllGetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exestrcat(winDir,"\\exmple.dll");//concentrate dll name with pathHINSTANCE DLL = LoadLibrary(winDir);//load example dllif(DLL==NULL){FreeLibrary((HMODULE)DLL);//if load fails exitreturn 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 nameif(mainDLLFunc==NULL){FreeLibrary((HMODULE)DLL);//if it fails exitreturn 0;}mainDLLFunc();//run exported functionFreeLibrary((HMODULE)DLL);}

反编译g++生成的二进制文件以查看发生了什么

main.cpp

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.cppreadelf -s main.o

输出包含:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

口译

我们看到:

  • efeg存储在与代码中同名的符号中

  • 其他符号被破坏了。让我们解开它们:

    $ c++filt _Z1fvf()$ c++filt _Z1hvh()$ c++filt _Z1gvg()

结论:以下两种符号类型都被没有破坏了:

  • 定义
  • 声明但未定义(Ndx = UND),在链接或运行时从另一个目标文件提供

所以调用时你需要extern "C"

  • 来自C++的C:告诉g++期待gcc产生的未损坏的符号
  • 来自C的C++:告诉g++生成未损坏的符号供gcc使用

在extern C中不起作用的东西

很明显,任何需要名称修饰的C++功能都无法在extern C中工作:

extern "C" {// Overloading.// error: declaration of C function ‘void f(int)’ conflicts withvoid f();void f(int i);
// Templates.// error: template with C linkagetemplate <class C> void f(C i) { }}

最小可运行C从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 __cplusplusextern "C" {#endifint f();#ifdef __cplusplus}#endif
#endif

c. c

#include "c.h"
int f(void) { return 1; }

运行:

g++ -c -o main.o -std=c++98 main.cppgcc -c -o c.o -std=c89 c.cg++ -o main.out main.o c.o./main.out

如果没有extern "C",链接将失败:

main.cpp:6: undefined reference to `f()'

因为g++期望找到一个损坏的f,而gcc没有产生。

GitHub上的示例

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" {#endifint 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.cg++ -c -o cpp.o -std=c++98 cpp.cppg++ -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找不到的残缺符号。

GitHub上的示例

当我包含来自C++的C头时,extern "c"在哪里?

在Ubuntu 18.04中测试。

extern "C"是一个链接规范,用于cpp源文件中的调用C函数。我们可以调用C函数、编写变量和包含标头。函数在extern实体中声明,它在外部定义。语法是

类型1:

extern "language" function-prototype

类型2:

extern "language"{function-prototype};

eg:

#include<iostream>using namespace std;
extern "C"{#include<stdio.h>    // Include C Headerint n;               // Declare a Variablevoid 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){////}

C++修饰函数名以从过程语言创建面向对象语言

大多数编程语言都不是建立在现有编程语言之上的。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;}

C编译器不会编译上面的示例,因为同一个函数printMe定义了两次(即使它们的参数int achar a不同)。

gcc-o printMe. c&&./printMe;
1错误。PrintMe定义了不止一次。

但是,C++编译器将编译上述示例。它不关心printMe定义两次。

++-o printMe. c&&./printMe;//打印我的地址

这是因为C++编译器根据其参数隐式重命名(mangles)函数。该语言被设计为面向对象-使用同名方法(函数)创建不同的类,并根据不同的参数覆盖方法名称(方法重写)。

extern "C"说的是“不要破坏C函数名称”

尽管C++是基于C语言构建的,但篡改可能会给C代码带来混乱。例如,假设我们有一个名为“父母. c”的遗留C文件,其中include函数名称来自不同的头文件,“父母. h”,“孩子. h”等。如果我们通过C++编译器运行“父母. c”,这将篡改该文件中的函数名称,并且它们将不再与头文件中指定的函数名称匹配。因此,“父母. h”和“孩子. h”头文件中的函数名称也需要篡改。这对于一些文件来说可能没问题,但如果C程序很复杂,那么修改可能会很慢并导致代码损坏,因此提供一个关键字来告诉C++编译器不要修改函数名称可能会很方便。

extern "C"关键字告诉C++编译器不要修改(重命名)C函数名称。

例如:

extern "C" void printMe(int a);

当混合C和C++时(例如,a.从C++调用C函数;b.从C调用C++函数),C++名称修饰会导致链接问题。从技术上讲,只有当被调用函数已经使用相应的编译器编译成二进制(很可能是*. a库文件)时,才会发生此问题。

所以我们需要使用extern“C”来禁用C++中的名称修饰。

这个答案是为不耐烦/有最后期限满足,只有一部分/简单的解释如下:

  • 在C++,你可以通过重载在类中具有相同的名称(例如,由于它们都是相同的名称,不能从dll等中按原样导出)解决这些问题的方法是将它们转换为不同的字符串(称为符号),符号占函数的名称,也是参数,因此即使这些函数具有相同的名称,也可以唯一标识(也称为名称修饰)
  • 在C中,您没有重载,函数名称是唯一的(因此,不需要单独的字符串来唯一标识函数名称,因此符号是函数名称本身)

所以
在C++,使用name修饰每个函数的唯一标识
在C中,即使没有名称修饰每个函数的唯一标识

要更改C++的行为,即指定特定函数的名称修饰不应该发生,您可以在函数名称之前使用extern"C",无论出于何种原因,例如从dll导出具有特定名称的函数以供其客户端使用。

阅读其他答案,以获得更详细/更正确的答案。

在不与其他好答案冲突的情况下,我将添加一些我的例子。

C++编译器到底做了什么:它在编译过程中修改了名称,因此我们需要特别告诉编译器治疗C实现。

当我们制作C++类并添加extern "C"时,我们告诉C++编译器我们正在使用C调用约定。

原因(我们从C++调用C实现):要么我们想从C++调用C函数,要么从C调用C++函数(C++类……等等在C中不起作用)。

由C编译器编译的函数vol f()和由C++编译器编译的同名函数vol f()不是同一个函数。如果您用C编写了该函数,然后尝试从C++调用它,那么链接器将查找C++函数而找不到C函数。

extern"C"告诉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); }

https://www.geeksforgeeks.org/extern-c-in-c/

gcc最近似乎也支持名称修改。即使在extern "c"中,如果您使用类或重载,它也会自动修改。

#include <stdio.h>extern "C"{

struct myint{int i;};
struct myint2{int a;myint2(int a): a(a) {};operator myint() const {return myint{a};}};
}
void f1(myint i){printf("%d", i.i);}
int main(){myint2 a(1);f1(a);}

我甚至使用了许多cpp功能。但是代码编译并运行正常。如果你nm,你可以看到main没有损坏,但myint是。