将外部“C”放入C++代码中具体做什么?

例如:

extern "C" {
   void foo();
}

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


它以这样一种方式更改函数的链接,即该函数可以从C调用。实际上,这意味着函数名不会被破坏。


extern“C”使C++中的函数名具有C链接(编译器不会损坏该名称),以便客户端C代码可以使用仅包含函数声明的C兼容头文件链接到(使用)您的函数。函数定义包含在二进制格式(由C++编译器编译)中,然后客户端C链接器将使用C名称链接到该格式。

由于C++有函数名的重载,而C没有,所以C++编译器不能仅将函数名用作链接到的唯一id,因此它通过添加有关参数的信息来破坏名称。C编译器不需要更改名称,因为您不能重载C中的函数名。当您声明函数在C++中具有外部“C”链接时,C++编译器不会将参数/参数类型信息添加到用于链接的名称中。

正如您所知,您可以显式指定每个单独声明/定义的外部“C”链接,或使用块将声明/定义序列分组以具有特定链接:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

如果您关心技术细节,请参见C++03标准第7.5节,这里有一个简短的总结(重点是外部“C”):

外部“C”是链接规范每个编译器都需要提供“C”链接链接规范只能出现在命名空间范围内所有函数类型、函数名和变量名都有语言链接参见Richard的评论:只有具有外部链接的函数名和函数名才有语言链接具有不同语言链接的两种函数类型是不同的类型,即使在其他方面相同连杆规格嵌套,内部规格决定最终连杆类成员忽略外部“C”最多一个具有特定名称的函数可以具有“C”链接(无论名称空间如何)外部“C”强制一个函数具有外部链接(不能使其成为静态的)参见Richard的评论:外部内部的静态“C”是有效的;这样声明的实体具有内部链接,因此没有语言链接从C++到用其他语言定义的对象以及从其他语言到用C++定义的对象的链接是由实现定义的,并且依赖于语言。只有当两种语言实现的对象布局策略足够相似时,才能实现这种链接


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

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

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

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

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


extern“C”是指由C++编译器识别,并通知编译器注意到的函数是(或将)以C样式编译的,因此在链接时,它链接到来自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"
}

我想这也能完成同样的事情。

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


仅仅通过在外部“C”中包装,并不能使任何C标头与C++兼容。当C标头中的标识符与C++关键字冲突时,C++编译器会对此进行投诉。

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

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

Kinda是有道理的,但在将C代码移植到C++时需要记住。


我之前对dll(动态链接库)文件使用了“extern”C“”,以使etc.main()函数“可导出”,以便稍后可以在dll的另一个可执行文件中使用。也许一个我过去使用它的例子会很有用。

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);
}

分解一个g++生成的二进制文件,看看发生了什么

主.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.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++过滤器_Z1fvf()$c++过滤器_Z1hvh()$c++过滤器_Z1gvg()

结论:以下两种符号类型均未受损:

定义已声明但未定义(Ndx=UND),将在链接或运行时从另一个对象文件提供

因此,在调用以下两个函数时,都需要外部“C”:

C++中的C:告诉g++期望gcc生成的未成文法的符号来自C的C++:告诉g++生成未成文法的符号供gcc使用

在extern C中不起作用的东西

很明显,任何需要更改名称的C++特性都不会在外部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函数只有一个可能的非损坏符号,因此不需要额外的工作。

主.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; }

Run:

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

如果没有外部“C”,链接将失败:

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

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

GitHub上的示例。

C示例中的最小可运行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;
}

每小时

#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);
}

Run:

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

如果没有外部“C”,则失败:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

因为g++生成了gcc找不到的损坏符号。

GitHub上的示例。

当我包含c++中的c头时,外部“c”在哪里?

C++版本的C头文件(如csdio)可能依赖于#pragma GCC system_headerhttps://gcc.gnu.org/onlinedocs/cpp/System-Headers.html提到:“在一些目标上,例如RS/6000AIX,当编译为C++时,GCC隐式地用‘extern‘C‘块包围所有系统头”,但我没有完全确认。像/usr/include/unistd.h这样的POSIX头包含在:我需要一个外部“C”块来包含标准的POSIX C头吗?通过__BEGIN_DECLS,在Ubuntu 20.04上再现__BEGIN_DECLS通过#include<features.h>包含。

在Ubuntu 18.04中测试。


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

类型1:

extern "language" function-prototype

类型2:

extern "language"
{
     function-prototype
};

eg:

#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)
{
    //
    //
}

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 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++是建立在C之上的,但破坏可能会导致C代码混乱。例如,假设我们有一个名为“parent.C”的遗留C文件,其中包含来自不同头文件的函数名,“parent.h”、“child.h”等。因此,“parent.h”和“child.h”头文件中的函数名也需要修改。对于一些文件来说,这可能没什么问题,但如果C程序很复杂,修改可能会很慢,并导致代码损坏,因此可以提供一个关键字,告诉C++编译器不要修改函数名。

extern“C”关键字告诉C++编译器不要篡改(重命名)C函数名。

例如:

extern“C”void printMe(int a);


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

因此,我们需要使用外部“C”来禁用C++中的名称篡改。


这个答案是针对那些不耐烦/有最后期限的人的,下面只是部分/简单的解释:

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

所以在C++中,名称mangling唯一标识每个函数在C语言中,即使没有名字,也可以唯一地标识每个函数

要更改C++的行为,即指定不应对特定函数进行名称篡改,可以在函数名称之前使用外部“C”,无论出于何种原因,例如从dll导出具有特定名称的函数,供其客户端使用。

阅读其他答案,了解更详细/更正确的答案。


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

C++编译器的作用是:它在编译过程中破坏了名称,因此我们需要告诉编译器要特别对待C实现。

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

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


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

extern“C”告诉C++编译器您有一个由C编译器编译的函数。一旦你告诉它它是由C编译器编译的,C++编译器就会知道如何正确调用它。

它还允许C++编译器以C编译器可以调用的方式编译C++函数。该函数将正式成为一个C函数,但由于它是由C++编译器编译的,因此它可以使用所有C++特性,并具有所有C++关键字。


请参阅下面的链接,该链接是Geeks for Geeks对外部“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似乎也支持名称篡改。即使在外部“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是损坏的。