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

What is the effect of extern “C” in C++?

将EDOCX1 0的代码放入C++代码中究竟做了什么?

例如:

1
2
3
extern"C" {
   void foo();
}


ExtEnter"C"使C++中的函数名具有"C"链接(编译器不修改名称),以便客户端C代码可以链接到(即使用)您的函数,使用一个"C"兼容的头文件,该文件只包含函数的声明。您的函数定义以二进制格式(由C++编译器编译),客户端C链接器将链接到使用"C"名称。

由于C++具有函数名的超载,C没有,C++编译器不能只使用函数名作为唯一的ID来链接,所以它通过添加关于参数的信息来修改名称。C编译器不需要修改名称,因为当您在C++中声明一个函数具有"外部""C"链接时,不能在C.中重载函数名,C++编译器不将参数/参数类型信息添加到用于链接的名称。

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

1
2
3
4
5
6
extern"C" void foo(int);
extern"C"
{
   void g(char);
   int i;
}

如果你关心技术性,它们被列在C++ 03标准的第7.5节中,这里是一个简短的摘要(着重于外部的C):

  • 外部"C"是一个链接规范
  • 每个编译器都需要提供"C"链接
  • 链接规范只能出现在命名空间范围内。
  • 所有函数类型、函数名和变量名都有语言链接参见Richard的注释:只有具有外部链接的函数名和变量名才有语言链接
  • 具有不同语言链接的两个函数类型是不同的类型,即使在其他方面相同
  • 连杆规格嵌套,内部决定最终连杆
  • 类成员忽略外部"c"
  • 最多一个具有特定名称的函数可以具有"c"链接(不考虑命名空间)
  • extern"c"forces a function to have external linkage(c an not make it static)see Richard's comment:'static'inside'extern"c"is valid;an entity so declared has internal linkage,and so does not have a language linkage
  • 从C++到其他语言定义的对象以及从其他语言中用C++定义的对象的链接是实现定义和语言依赖的。只有当两种语言实现的对象布局策略足够相似时,才能实现这种链接。


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

您经常会在C头中看到这样的代码:

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern"C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

实现的是,它允许您使用C++代码使用C头文件,因为宏定义为"y*cPLUS PLUS"。但是,您还可以将它与您的遗留C代码一起使用,宏没有被定义,因此它不会看到唯一的C++构造。

虽然,我也见过C++代码,例如:

1
2
3
extern"C" {
#include"legacy_C_header.h"
}

我想,它的作用是一样的。

不知道哪条路更好,但我都看过。


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

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

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

因此,如果您指定一个函数为extern c,编译器不会用它执行名称管理,它可以直接使用其符号名作为函数名访问。

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


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

主CPP

1
2
3
4
5
6
7
8
9
10
void f() {}
void g();

extern"C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

使用GCC4.8 Linux ELF输出编译:

1
g++ -c main.cpp

反编译符号表:

1
readelf -s main.o

输出包括:

1
2
3
4
5
6
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

解释

我们看到:

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

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

    1
    2
    3
    4
    5
    6
    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()

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

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

因此,您在拨打以下电话时需要同时使用extern"C"

  • C从C++中告诉EDCOX1×0,以期待EDCOX1×6产生的未加密符号。
  • C中的C++:告诉EDCOX1×0 }以生成EDCOX1(6)使用的未加密符号。

在外部C中不起作用的东西

很明显,任何需要名称修改的C++特性在EDOCX1 9中都不起作用:

1
2
3
4
5
6
7
8
9
10
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

为了完整性和NeWB,在C++项目中如何使用C源文件?

从C++调用C非常简单:每个C函数只有一个可能的非标记符号,因此不需要额外的工作。

主CPP

1
2
3
4
5
6
7
#include <cassert>

#include"c.h"

int main() {
    assert(f() == 1);
}

C.H

1
2
3
4
5
6
7
8
9
10
11
12
13
#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

C.C

1
2
3
#include"c.h"

int f(void) { return 1; }

运行:

1
2
3
4
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",链接将失败:

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

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

Github上的示例。

C实例中的最小可运行C++

调用C++是有点困难的:我们必须手动创建我们想要公开的每个函数的非破坏版本。

这里说明了如何将C++函数重载暴露为C。

主C

1
2
3
4
5
6
7
8
9
#include

#include"cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

CPP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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);
}

运行:

1
2
3
4
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",则失败的原因是:

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

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

Github上的示例。

在Ubuntu 18.04中测试。


从程序语言创建面向对象的语言

大多数编程语言并没有在现有编程语言的顶端建立。C++is built on-top of C,and furtherre is an object-oriented programming language built from a procedural programming language,and for reason there C+++keywords like EDOCX1>0

Let's look at the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i
"
, a);
}

void printMe(char a) {
  printf("char: %c
"
, a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

a C编译器将不编译上述例子,因为同一函数printMe是确定两个参数(即使它们有不同的int aVS EDOCX1〕〔3〕)。

gcc -o printMe printMe.c && ./printMe;
1 error. PrintMe is defined more than once.

a C+compiler will compile the above example.它不在乎是确定的两个。

BLCK1/

这是因为一个C++编译隐含重命名(芒果)函数基于它们的参数。在C类中,这一特征没有得到支持。然而,当C++被建成在C上时,语言的设计是客观的,并且需要支持以不同参数为基础的创建不同类别(功能)的能力。

Extern says"Don't mangle function names"

然而,想象一下,如果遗产"Parent.c"File is run through a C++compiler,那么功能名称将被删除,而功能名称将不再与功能名称"Parent.h","Parent.h","Child.h"等名称匹配。UNCCtion names in those external files would also need to be mangled.芒果函数的名称跨越复杂的C程序,其中有许多依赖性,可以引导破译代码;因此,它可能适合于提供一个关键词,它可以告诉C++编译器而不是芒果函数的名称。

关键词告诉C++编译器,而不是芒果(重命名)函数的名称。使用范例:EDOCX1&7


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


没有任何C头可以通过只在外部"C"中封装而与C++兼容。当C标头中的标识符与C++关键字冲突时,C++编译器会抱怨这一点。

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

1
2
3
4
5
extern"C" {
struct method {
    int virtual;
};
}

有点有意义,但是在将C代码移植到C++时要记住一些东西。


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


外部C是指由C++编译器识别并通知编译器所指出的函数是(或将)以C方式编译的。这样,当链接时,它从C链接到正确版本的函数。


我在前面使用了"extern"c",将dll(动态链接库)文件设置为等。main()函数"exportable",以便以后在dll的另一个可执行文件中使用。也许一个我曾经使用它的例子是有用的。

动态链接库

1
2
3
4
5
6
7
8
9
10
11
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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);
}


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

类型1:

1
extern"language" function-prototype

类型2:

1
2
3
4
extern"language"
{
     function-prototype
};

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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)
{
    //
    //
}


This answer is for the insext/have deadlines to meet to,only a part/simple explanation is below:

  • In C++,you can have same name in class via overloading(example,since they are all same name can't exported as-is from DLL,etc).These problems is converted to different strings(called symbols),symbols accounts the name of function,also the examination,so each of these functions even with same name,can be uniqueIdentified(also called,name mangling)
  • 在C中,你没有过载,函数名称是独一无二的(因此,不需要一个函数名称独一无二的分隔字符串,所以符号是函数名称本身)。

So在C++中,名称芒果单一的识别每个功能在C中,即使没有名字曼格林独特的身份每个功能

为了改变C++的行为,具体地说,MANGLING的名称不应发生某个特定功能,在函数名称之前,您可以使用"C"以外的函数,以任何理由,比如从DLL导出一个特定名称的函数,以供客户使用。

阅读其他答案,以便提供更详细/更正确的答案。


When mixing C and C+(I.E.,A.Calling C function from C++;and B.Calling C+++function from C),the C+++name mangling causes linking problems.Technically speaking,this issue happens only when the callee functions have already compiled into binary(most likely,a*a library file)using the corresponding compiler.

所以我们需要使用外部"C"来解读"C+"中的名字。