外部“C”在C ++中有什么影响?

时间:2009-06-25 02:10:08

标签: c++ c linkage name-mangling extern-c

extern "C"放入C ++代码到底是做什么的?

例如:

extern "C" {
   void foo();
}

16 个答案:

答案 0 :(得分:1382)

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

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

您知道,您可以明确指定每个声明/定义的“C”链接,或使用块将一系列声明/定义分组以具有特定的链接:

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

如果您关心技术问题,它们列在C ++ 03标准的7.5节中,这里是一个简短的摘要(重点是外部“C”):

  • extern“C”是一个链接规范
  • 每个编译器必需以提供“C”链接
  • 链接规范仅在命名空间范围内发生
  • 所有函数类型,函数名和变量名都有语言链接 See Richard's Comment: 只有具有外部链接的函数名和变量名具有语言链接
  • 具有不同语言联系的两种函数类型是不同的类型,即使它们是相同的
  • 连接规范嵌套,内部确定最终链接
  • 对于班级成员,
  • extern“C”被忽略
  • 最多一个具有特定名称的函数可以具有“C”链接(无论命名空间如何)
  • extern“C”强制函数具有外部链接(不能使其静止) See Richard's comment: '静态'内部'extern“C”'是有效;如此声明的实体具有内部链接,因此没有语言链接
  • 从C ++到其他语言定义的对象以及从其他语言在C ++中定义的对象的链接是实现定义和语言相关的。只有在两种语言实现的对象布局策略足够相似的情况下才能实现这种联系

答案 1 :(得分:279)

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

你会经常看到C标题中的代码如下:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

这实现了它允许您将C头文件与C ++代码一起使用,因为将定义宏“__cplusplus”。但是仍然可以将它与遗留的C代码一起使用,其中宏 NOT 已定义,因此它不会看到唯一的C ++构造。

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

extern "C" {
#include "legacy_C_header.h"
}

我想象的完成了同样的事情。

不确定哪种方式更好,但我已经看到了两种方式。

答案 2 :(得分:195)

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

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

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

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

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

答案 3 :(得分:183)

反编译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(); }

使用GCC 4.8 Linux ELF输出编译:

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

为了完整性和新手,请参阅:How to use C source files in a C++ project?

从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++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

C.C

#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++期望找到一个错误的fgcc没有产生。

Example on GitHub

来自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" {
#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无法找到的错位符号。

Example on GitHub

在Ubuntu 18.04中测试过。

答案 4 :(得分:39)

C ++破坏函数名称以从过程语言

创建面向对象的语言

大多数编程语言不是在现有编程语言的基础上构建的。 C ++是在C语言之上构建的,而且它是一种使用过程编程语言构建的面向对象的编程语言,因此有像extern "C"这样的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&amp;&amp; ./printMe;
   1错误。 PrintMe定义不止一次。

C ++编译器将编译上面的示例。它并不关心printMe被定义两次。

  

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

这是因为C ++编译器根据其参数隐式重命名(mangles)函数。在C中,不支持此功能。但是,当C ++是基于C构建的时,该语言被设计为面向对象的,并且需要支持使用相同名称的方法(函数)创建不同类的能力,并覆盖方法(method overriding)基于不同的参数。

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

答案 5 :(得分:26)

它改变函数的链接,使得函数可以从C调用。实际上,这意味着函数名称不是mangled

答案 6 :(得分:25)

只需包装extern“C”,就不能使任何C-header与C ++兼容。当C-header中的标识符与C ++关键字冲突时,C ++编译器会抱怨这一点。

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

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

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

答案 7 :(得分:19)

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

答案 8 :(得分:12)

extern“C”意味着由C ++编译器识别,并通知编译器所提到的函数是(或将)以C风格编译的。因此,在链接时,它链接到C的正确版本的函数。

答案 9 :(得分:6)

我使用了&#34; extern&#34; C&#34;&#39;之前为dll(动态链接库)文件制作等main()函数&#34;可导出&#34;所以它可以在以后用于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);
}

答案 10 :(得分:5)

extern "C"是一个链接规范,用于调用C函数 Cpp源文件。我们可以调用C函数,写变量,&amp;包含标题。函数在extern实体和&amp;中声明。它在外面定义。语法是

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

答案 11 :(得分:2)

这个答案是针对急躁的人/有最后期限的,下面仅是部分/简单的解释:

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

所以
在C ++中,通过名称修饰唯一地标识每个函数
在C语言中,即使没有名称,也无法唯一标识每个函数的身份

要更改C ++的行为,即要为特定功能指定名称改写 ,可以在功能前使用 extern“ C” 名称,无论出于何种原因,例如从dll导出具有特定名称的函数以供其客户端使用。

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

答案 12 :(得分:1)

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

所以我们需要使用extern&#34; C&#34;在C ++中禁用名称修改。

答案 13 :(得分:0)

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

C ++编译器的确切作用是:它会在编译过程中破坏名称,因此我们需要告诉编译器专门进行 treat C实现。

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

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

答案 14 :(得分:0)

由C编译器编译的函数void f()和由C ++编译器编译的具有相同名称的函数void f()是不同的函数。如果您使用C编写了该函数,然后尝试从C ++调用它,则链接器将查找C ++函数,但找不到C函数。

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

它还允许C ++编译器以C编译器可以调用它的方式来编译C ++函数。该函数正式是C函数,但是由于它是由C ++编译器编译的,因此它可以使用所有C ++功能并具有所有C ++关键字。

答案 15 :(得分:-1)

请参阅下面的链接,该链接是 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/