提问



extern "C"放到C ++代码中究竟做了什么?


例如:


extern "C" {
   void foo();
}

最佳参考


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


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


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


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


如果你关心技术问题,它们列在C ++ 03标准的7.5节中,这里是一个简短的总结(重点是externC):



  • externC是一个链接规范

  • 每个编译器必需以提供C链接

  • 链接规范仅在命名空间范围内发生

  • 所有函数类型,函数名和变量名都有语言链接 参见Richard的评论:只有具有外部链接的函数名和变量名具有语言链接

  • 具有不同语言联系的两种函数类型是不同的类型,即使它们是相同的

  • 连接规范嵌套,内部确定最终链接

  • 对于班级成员
  • ,将忽略externC
  • 最多一个具有特定名称的函数可以具有C链接(无论命名空间如何)

  • externC强制函数具有外部链接(不能使其静止) 参见Richard的评论:静态在externC中是有效的;如此声明的实体具有内部链接,因此没有语言链接

  • 从C ++到其他语言定义的对象以及从其他语言在C ++中定义的对象的链接是实现定义的和语言相关的。只有在两种语言实现的对象布局策略足够相似的情况下才能实现这种联系


其它参考1


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


您经常会在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


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


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


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


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


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

其它参考3


让s 反编译生成的目标文件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 ELF输出编译:


g++ -c a.cpp


反编译符号表:


readelf -s a.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) { }
}

其它参考4


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

创建面向对象的语言

大多数编程语言都没有构建在现有的编程语言之上.C ++建立在C语言之上,而且它是一种基于过程编程语言的面向对象编程语言,因此有C ++关键字像extern那样提供与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构建的时,该语言被设计为面向对象的,并且需要支持使用相同名称的方法(函数)创建不同类的能力,并且基于不同的方法覆盖方法(方法重写)参数[40] [41]


Extern说不要破坏功能名称



但是,假设我们有一个名为parent.c的遗留C文件,include的函数来自其他遗留C文件,parent.h,child.h等。如果遗留的父。 c文件是通过C ++编译器运行的,那么函数名称将被破坏,它们将不再匹配parent.h,child.h等中指定的函数名称 - 所以函数名称在那些外部文件也需要被修改。这可能会变得非常混乱。因此,提供一个可以告诉C ++编译器不会破坏函数名的关键字可能会很方便。


extern关键字告诉C ++编译器不要破坏(重命名)函数名。用法示例:extern void printMe(int a);

其它参考5


它改变了函数的链接,使得函数可以从C调用。在实践中,这意味着函数名称不会受损。[42]

其它参考6


没有任何C-header将使用externC进行编译。当C-header中的标识符与C ++关键字冲突时,C ++编译器会抱怨这一点。


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



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


Kinda有意义,但在将C代码移植到C ++时需要牢记。

其它参考7


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

其它参考8


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

其它参考9


我使用externC之前为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);
}


可执行程序


#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


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


类型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


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


所以我们需要使用externC来禁用C ++中的名称修改。

其它参考12


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



  • 在C ++中,您可以通过重载在类中具有相同的名称(例如,因为它们都是同名的,不能从dll中导出,等等)解决这些问题的方法是将它们转换为不同的字符串(称为符号),符号表示函数的名称,也包括参数,因此每个函数甚至具有相同的名称,可以唯一标识(也称为名称修改)

  • 在C中,你没有超载,函数名是唯一的(因此,不需要单独的字符串来唯一地标识函数名,因此符号本身就是函数名)。



所以结果
在C ++中,名称为每个函数的唯一标识
在C中,即使没有名称,每个函数都会对其进行唯一标识


要更改C ++的行为,也就是说,为特定函数指定名称错误不应该,可以在函数名之前使用 externC,无论什么原因,比如从dll导出具有特定名称的函数,供其客户使用。


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