30

我正在学习编程语言课程,我们正在讨论extern "C"声明。

除了“它接口 C 和 C++”之外,这个声明如何在更深层次上工作?这又如何影响程序中发生的绑定?

4

9 回答 9

51

extern "C"用于确保后面的符号没有被破坏(装饰)。


例子:

假设我们在一个名为的文件中有以下代码test.cpp

extern "C" {
  int foo() {
    return 1;
  }
}

int bar() {
  return 1;
}

如果你跑gcc -c test.cpp -o test.o

看一下符号名称:

00000010 T _Z3barv

00000000 T 富

foo()保留它的名字。

于 2010-03-08T17:52:46.707 回答
26

让我们看一个可以在 C 和 C++ 中编译的典型函数:

int Add (int a, int b)
{
    return a+b;
}

现在在 C 中,该函数在内部称为“_Add”。而 C++ 函数在内部使用一个名为 name-mangling 的系统被称为完全不同的东西。它基本上是一种命名函数的方法,以便具有不同参数的相同函数具有不同的内部名称。

因此,如果在 add.c 中定义了 Add(),并且您在 add.h 中有原型,如果您尝试将 add.h 包含在 C++ 文件中,则会遇到问题。因为 C++ 代码正在寻找一个名称与 add.c 中的函数不同的函数,所以您将收到链接器错误。要解决该问题,您必须通过以下方法包含 add.c :

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

现在 C++ 代码将链接到 _Add 而不是 C++ 名称损坏的版本。

这是表达式的用途之一。底线,如果您需要在 C++ 程序中编译严格为 C 的代码(通过 include 语句或其他方式),您需要用 extern "C" { ... } 声明包装它。

于 2010-03-08T17:56:54.003 回答
10

当您使用 extern "C" 标记代码块时,您是在告诉系统使用 C 样式链接。

这主要影响链接器破坏名称的方式。您可以从链接器中获得标准的 C 风格命名,而不是使用 C++ 样式名称修饰(支持运算符重载更复杂)。

于 2010-03-08T17:53:36.427 回答
6

需要注意的是,extern "C"还修改了函数的类型。它不仅修改了较低级别的内容:

extern "C" typedef void (*function_ptr_t)();

void foo();

int main() { function_ptr_t fptr = &foo; } // error!

of 的类型&foo不等于 typedef 指定的类型(尽管代码被某些编译器接受,但不是所有编译器都接受)。

于 2010-03-08T21:39:37.840 回答
5

在 C++ 中,函数的名称/符号实际上被重命名为其他名称,以便不同的类/命名空间可以具有相同签名的函数。在 C 中,函数都是全局定义的,不需要这种自定义的重命名过程。

为了使 C++ 和 C 相互交流,“extern C”指示编译器不要使用 C 约定。

于 2010-03-08T17:54:36.643 回答
4

extern C 会影响 C++ 编译器的名称修饰。它是一种让 C++ 编译器不破坏名称的方法,或者更确切地说,以与 C 编译器相同的方式来破坏它们。这就是它接口 C 和 C++ 的方式。

举个例子:

extern "C" void foo(int i);

将允许在 C 模块中实现该函数,但允许从 C++ 模块中调用它。

当试图让 C 模块调用 C++ 模块中定义的 C++ 函数(显然 C 不能使用 C++ 类)时,麻烦就来了。C 编译器不喜欢extern "C".

所以你需要使用这个:

#ifdef __cplusplus
extern "C" {
#endif

void foo(int i);

#ifdef __cplusplus
}
#endif

现在,当它出现在头文件中时,C 和 C++ 编译器都会对声明感到满意,它现在可以在 C 或 C++ 模块中定义,并且可以由 C 和 C++ 代码调用。

于 2010-03-08T18:06:09.377 回答
3

extern "C" 表示封闭的代码使用 C 风格的链接和名称修改。C++ 使用更复杂的名称修饰格式。这是一个例子:

http://en.wikipedia.org/wiki/Name_mangling

int example(int alpha, char beta);

在 C 中:_example

在 C++ 中:__Z7exampleic

更新:正如 GManNickG 在评论中指出的那样,名称修饰的模式取决于编译器。

于 2010-03-08T17:59:35.400 回答
1

extern "C", 是声明一个带有 C 绑定的函数的关键字,因为 C 编译器和 C++ 编译器会将源代码转换为目标文件中的不同形式:

例如,一个代码片段如下:

int _cdecl func1(void) {return 0}
int _stdcall func2(int) {return 0}
int _fastcall func3(void) {return 1}

32 位 C 编译器将按如下形式转换代码:

_func1
_func2@4
@func3@4

在 cdecl 中,func1 将翻译为 ' _name '

在 stdcall 中,func2 将转换为 ' _name@X '

在 fastcall 中,func2 将翻译为 ' @name@X '

' X ' 表示参数列表中参数的字节数。

Windows 上的 64 位约定没有前导下划线

在 C++ 中,引入了类、模板、命名空间和运算符重载,因为不允许两个函数同名,C++ 编译器在符号名称中提供类型信息,

例如,一个代码片段如下:

int func(void) {return 1;}
int func(int) {return 0;}
int func_call(void) {int m=func(), n=func(0);}

C++ 编译器会将代码翻译如下:

int func_v(void) {return 1;}
int func_i(int) {return 0;}
int func_call(void) {int m=_func_v(), n=_func_i(0);}

'_v' 和 '_i' 是 'void' 和 'int' 的类型信息

于 2015-04-30T13:36:51.357 回答
-2

这是来自msdn的报价

“extern 关键字声明一个变量或函数,并指定它具有外部链接(其名称在定义它的文件之外的文件中可见)。修改变量时,extern 指定变量具有静态持续时间(它已分配当程序开始时,程序结束时释放)。变量或函数可以在另一个源文件中定义,或者稍后在同一文件中定义。在文件范围内的变量和函数的声明默认是外部的。

http://msdn.microsoft.com/en-us/library/0603949d%28VS.80%29.aspx

于 2010-03-08T17:52:02.420 回答