中extern的用法 - C++中extern“C”的效果是什么?



linux extern (8)

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

AC编译器不会编译上面的例子,因为相同的函数printMe被定义了两次(即使它们具有不同的参数int a vs char a )。

gcc -o printMe printMe.c && ./printMe;
1错误。 PrintMe定义了多次。

一个C ++编译器将编译上面的例子。 它并不在意printMe被定义了两次。

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

这是因为C ++编译器根据参数隐式重命名( en.wikipedia.org/wiki/Name_mangling )函数。 在C中,此功能不受支持。 但是,当C ++构建于C之上时,该语言被设计为面向对象,并且需要支持使用相同名称的方法(函数)创建不同类的能力,以及基于不同方法覆盖方法( 方法重写 )参数。

Extern说“不要破坏功能名称”

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

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

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

例如:

extern "C" {
   void foo();
}

Answer #1

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

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

大家知道,您可以明确指定每个单独声明/定义的“C”链接,或者使用一个块将一系列声明/定义分组,以便进行特定的链接:

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

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

  • extern“C”是一个连接规范
  • 每个编译器都需要提供“C”链接
  • 链接规范只能出现在命名空间范围内
  • 所有函数类型,函数名称和变量名称都具有语言链接 请参见Richard的注释:只有具有外部链接的函数名称和变量名称具有语言链接
  • 具有不同语言链接的两种功能类型是不同的类型,即使其他方面相同
  • 联动规格嵌套,内在联系规定最终联动
  • 对于班级成员,外部“C”被忽略
  • 至多有一个具有特定名称的函数可以具有“C”连接(不管名称空间)
  • extern“C”强制一个函数具有外部链接(不能使其静态) 参见Richard的评论: 'extern'中的'static'C''是有效的; 如此宣布的实体具有内部联系,因此没有语言联系
  • 从C ++到其他语言中定义的对象与其他语言中定义在C ++中的对象之间的链接是实现定义的和语言相关的。 只有两种语言实现的对象布局策略足够相似,才能实现这种连接

Answer #2

extern "C"是一个链接规范,用于在Cpp源文件中 调用C函数 。 我们可以调用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)
{
    //
    //
}

Answer #3

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

你会经常在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"
}

我想象的是完成了很多相同的事情。

不知道哪种方式更好,但我都看到了。


Answer #4

它以一种可以从C中调用函数的方式改变函数的链接。在实践中,这意味着函数名称不会mangled


Answer #5

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


Answer #6

我之前使用'extern“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);
}

可执行程序

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

Answer #7

我们来反编译生成的目标文件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产生的unmangled符号
  • 来自C的C ++:告诉g++gcc使用生成unmangled符号

在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) { }
}




extern-c