这到底是什么把 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++ 编译器不会将参数/参数类型信息添加到用于链接的名称中。

正如您所知,您可以显式指定每个单独的声明/定义的“C”链接,或者使用块对一系列声明/定义进行分组以具有特定的链接:

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

如果您关心技术细节,它们列在 C++03 标准的 7.5 节中,这里是一个简短的摘要(重点是 extern "C"):

  • extern "C" 是链接规范
  • 每个编译器都是 必需的 提供“C”连接
  • 链接规范应仅出现在名称空间范围内
  • 所有函数类型、函数名称和变量名称都具有语言链接 请参阅理查德的评论: 只有具有外部链接的函数名和变量名才具有语言链接
  • 具有不同语言链接的两个函数类型是不同的类型,即使其他方面相同
  • 联动规格嵌套,里面的一个决定了最终的联动
  • 类成员的 extern "C" 被忽略
  • 至多一个具有特定名称的函数可以具有“C”链接(无论命名空间如何)
  • extern "C" 强制函数具有外部链接(不能使其静态) 请参阅理查德的评论: 'extern "C"' 内的 'static' 有效;如此声明的实体具有内部链接,因此不具有语言链接
  • 从 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 ++使用所谓的名称重整,其将函数名和所有必需的信息(像的参数的数目和尺寸)成仅由编译器和链接器处理一些奇怪的前瞻性字符串。

因此,如果你指定一个函数是外部C,编译器不执行名称与它重整和它可以直接 使用它的符号名称作为函数名访问。

此同时使用dlsym()dlopen()调用这样的功能来得心应手。

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

主程序

void f() {}
void g();

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

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

使用 GCC 4.8 Linux 编译 极低频 输出:

g++ -c main.cpp

反编译符号表:

readelf -s main.o

输出包含:

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 存储在与代码同名的符号中

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

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

结论:以下两种符号类型都是 不是 损坏:

  • 定义的
  • 已声明但未定义 (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 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.h

#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

抄送

#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 没有产生。

GitHub 上的示例.

来自 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;
}

程序文件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

程序文件

#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 找不到。

GitHub 上的示例.

在 Ubuntu 18.04 中测试。

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).

海湾合作委员会-o printMe printMe.c&&./printMe;
1错误。PrintMe定义超过一次。

C++编译器的将汇编上述的例子。它不关心 printMe 定义的两倍。

g++-o printMe printMe.c&&./printMe;

这是因为一个C++编译器隐含地重新命名()的功能基于他们的参数。在C,这一要素没有得到支持。然而,当C++是建立在C、语言设计是面向对象,并需要支持创造能力不同类别的方法(功能)的相同的名称,并为替代方法(方法的压倒一切的)基于不同的参数。

extern "C" 说"不轧C功能的名字"

然而,想象我们有一个遗留C的文件,名为"父母。c", includes函数名来自其他遗留C的文件,"父母。h","孩子。h",等等。如果遗产"的父母。c"文件通过一个C++编译器,那么功能名称将会出错,并且他们将不再相匹配功能的指定的名称中的"父母。h","孩子。h",等等-所以能在这些外部文件还将需要错位。重整能跨越一个复杂的C节目,这些有很多的依赖性,可能导致破码;所以它可能是方便提供一个关键字可以告诉C++编译器不要损坏功能名称。

extern "C" 关键字告诉C++编译器不轧(重新命名)C功能名称。例的使用情况: extern "C" void printMe(int a);

它改变联系的一个函数,在这样一种方式的功能是可调用从C。在实践中意味着功能的名字是不是 错位.

不任何C-头可通过在外部的“C”仅仅包裹与C兼容++。当标识符用C C-头冲突++关键字C ++编译器会抱怨。

例如,我看到下面的代码无法在一克++:结果

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

有点儿是有道理的,但是东西移植C代码到C ++时要记住。

它通知C ++编译器来查找的那些功能的名称在链接时C型,因为在C和C ++编译函数的名称是在链接阶段不同。

外部的“C”是指由C ++编译器识别并通知编译器该注意到的功能是(或将要)用C风格编译。所以,虽然联,它由C链接到的功能的正确版本。

我使用的“外部的‘C’”之前,DLL(动态链接库)文件,以使等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);
}

extern "C"是用于为呼叫C函数 .cpp的源文件连杆规范。我们可以的呼叫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)
{
    //
    //
}

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

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

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

改变C++的行为,即指定名称修改 不应该 对于特定功能发生,您可以使用 外部“C” 在函数名称之前,无论出于何种原因,例如从 dll 中导出具有特定名称的函数以供其客户端使用。

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

当混合C和C ++(即,从C ++主叫C函数;和b。调用C从C ++函数)时,C ++名称重整原因联的问题。从技术上讲,当调用函数必须使用相应的编译器已经被编译成二进制(最有可能的,一个* .A库文件)这个问题只发生。

因此,我们需要利用外部的“C”,以禁用名称用C ++压延

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top