40

根据 (c) ANSI ISO/IEC 14882:2003,第 127 页:

联动规格嵌套。当链接规范嵌套时,最里面的一个确定语言。链接规范不建立范围。链接规范应仅出现在命名空间范围 (3.3) 中。在链接规范中,指定的语言链接适用于由声明引入的所有函数声明符、函数名和变量名的函数类型。

extern "C" void f1(void(*pf)(int));
// the name f1 and its function type have C language
// linkage; pf is a pointer to a C function

extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the
// function's type has C language linkage

extern "C" FUNC f3;
// the name of function f3 and the function's type
// have C language linkage

void (*pf2)(FUNC*);
// the name of the variable pf2 has C++ linkage and
// the type of pf2 is pointer to C++ function that
// takes one parameter of type pointer to C function

这是什么意思呢?例如,f2()函数有什么链接,C 或 C++ 语言链接?

正如@Johannes Schaub 所指出的,标准中没有真正解释这意味着什么,因此在不同的编译器中可以有不同的解释。

请解释目标文件中的差异:

  • 具有 C 语言链接和 C++ 语言链接的函数名称。
  • 具有 C 语言链接和 C++ 语言链接的函数类型。
4

7 回答 7

18

语言链接是用于链接C++non-C++代码片段之间的术语。通常,在 C++ 程序中,所有函数名、函数类型甚至变量名都具有默认的 C++ 语言链接。

C可以使用预定义的链接说明符将 C++ 目标代码链接到使用其他源语言(如 )生成的另一个目标代码。

你必须知道 的概念name mangling,它对函数名、函数类型和变量名进行编码,以便为它们生成一个唯一的名称。这允许链接器区分常用名称(如在函数重载的情况下)。将 C 模块与使用 C++ 编译器编译的库或目标文件链接时,不希望进行名称修改。为了防止这种情况下的名称修改,使用了链接说明符。在这种情况下,extern "C"是链接说明符。举个例子(这里提到的c++代码):

typedef int (*pfun)(int);  // line 1
extern "C" void foo(pfun); // line 2
extern "C" int g(int)      // line 3
...
foo( g ); // Error!        // line 5

第 1 行声明pfun指向 C++ 函数,因为它缺少链接说明符。

因此,第 2 行将 foo 声明为一个 C 函数,它接受一个指向 C++ 函数的指针。

第 5 行尝试使用指向 g、C 函数、类型不匹配的指针调用 foo。

函数名称链接的差异:

让我们拿两个不同的文件:

一个有extern "c"链接(file1.cpp):

#include <iostream>
using namespace std;

extern "C"
{
void foo (int a, int b)
{
    cout << "here";
}
}

int main ()
{
    foo (10,20);
    return 0;
}

一个没有extern "c"链接(file2.cpp):

#include <iostream>
using namespace std;

void foo (int a, int b)
{
    cout << "here";
}

int main ()
{
    foo (10,20);
    return 0;
}

现在编译这两个并检查 objdump。

# g++ file1.cpp -o file1
# objdump -Dx file1

# g++ file2.cpp -o file2
# objdump -Dx file2

使用 extern "C" 链接,函数没有名称修饰foo。因此,任何使用它的程序(假设我们用它制作了一个共享库)都可以直接调用 foo (使用 and 之类的辅助函数dlsymdlopen,而无需考虑任何名称修改效果。

0000000000400774 <foo>:
  400774:   55                      push   %rbp
  400775:   48 89 e5                mov    %rsp,%rbp
....
....
  400791:   c9                      leaveq 
  400792:   c3                      retq   

0000000000400793 <main>:
  400793:   55                      push   %rbp
  400794:   48 89 e5                mov    %rsp,%rbp
  400797:   be 14 00 00 00          mov    $0x14,%esi
  40079c:   bf 0a 00 00 00          mov    $0xa,%edi
  4007a1:   e8 ce ff ff ff          callq  400774 <foo>
  4007a6:   b8 00 00 00 00          mov    $0x0,%eax
  4007ab:   c9                      leaveq 

另一方面,当 noextern "C"被使用时, func:foo被一些预定义的规则(正在使用的编译器/链接器已知)破坏,因此应用程序不能直接从中调用它,并将名称指定为foo. 但是,如果您愿意,您可以使用损坏的名称(_Z3fooii在这种情况下)来调用它,但出于显而易见的原因,没有人使用它。

0000000000400774 <_Z3fooii>:
  400774:   55                      push   %rbp
  400775:   48 89 e5                mov    %rsp,%rbp
 ...
...
  400791:   c9                      leaveq 
  400792:   c3                      retq   

0000000000400793 <main>:
  400793:   55                      push   %rbp
  400794:   48 89 e5                mov    %rsp,%rbp
  400797:   be 14 00 00 00          mov    $0x14,%esi
  40079c:   bf 0a 00 00 00          mov    $0xa,%edi
  4007a1:   e8 ce ff ff ff          callq  400774 <_Z3fooii>
  4007a6:   b8 00 00 00 00          mov    $0x0,%eax
  4007ab:   c9                      leaveq 
  4007ac:   c3                      retq   

这个页面也是这个特定主题的好读物。

关于调用约定的一篇很好且解释清楚的文章:http: //www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

于 2011-05-19T14:56:28.443 回答
2

“名称 f2 具有 C++ 语言链接” 在 C++ 语言链接中,不仅函数的名称定义它,而且它的参数类型和返回值也定义。在这种情况下,您有: void f2(void); 但你可以用它来定义: void f2(int a); 没有冲突,因为链接会将它们视为不同的类型,这是您在 C 语言中无法做到的。

“函数的类型有C语言链接”我不知道细节,但我知道它的高级。基本上,它使 C++ 编译的函数可以从 C 链接。如果我没记错的话,在 C 和 C++ 中,将参数传递给函数的方式是不同的。在这种情况下,函数 f2 将像 C 编译器那样传递参数。这样,该函数将可以从 C 和 C++ 链接。

于 2011-05-18T08:22:41.250 回答
2
extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the
// function's type has C language linkage

该名称FUNC使用“C”链接声明,因为它extern "C"在第一行说明。

该名称f2具有 C++ 链接,因为这是默认值,并且在第二行没有给出其他链接。

namef2用于引用具有 C 链接的函数这一事实不会改变name的链接。

于 2011-05-19T15:21:40.340 回答
2

它与程序的ABI(应用程序二进制接口)有关。

API 指定程序源代码的外部接口,ABI 指定程序二进制代码(编译版本)的外部接口。


最初,C 函数只是有几种不同的形式。就像是

int foo(int);

将由编译器以下划线为前缀,形成_foo,然后导出以供其他应用程序使用。

然而,这还不够。例如,如果您查看 Windows API,您会看到如下内容:

DWORD CreateWindowW(...);        //Original parameters
DWORD CreateWindowExW(..., ...); //More parameters

这是因为无法仅通过查看函数名称来区分函数的重载,因此人们开始通过添加Ex后缀(或类似名称)来更改它们。

这变得非常丑陋,并且它仍然不允许运算符重载,这是 C++ 中的特色。正因为如此,C++ 提出了name mangling,将额外的信息放入函数的名称中,例如其参数的数据类型,并使其具有大量@符号的神秘感。

一切都很好,只是它没有完全标准化

当然,随着新的语言和编译器的出现,每一种都提出了自己的方案,其中一些与其他的不兼容。因此,如果您需要导入或导出外部函数,则需要指定编译器应查找的 ABI 类型,因此extern "C++"您有。

于 2011-05-19T19:20:10.290 回答
2

这是什么意思呢?例如,f2() 函数有什么链接,C 或 C++ 语言链接?

extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the 
// function's type has C language linkage 

您所说的“f2() 函数”的链接有两个方面:

  • 符号表(具有 C++ 语言链接)中其名称的修饰与否,以及
  • 调用函数时所需的 C 或 C++ 调用约定 (C)。

打电话给f2()你,在目标文件中找到它的名字又名符号,这将是“名为 f2 的函数不带参数”的错位版本。您可以通过编译上述代码并检查对象(例如,使用 GNU 工具nm --demangle)来简单地验证这一点。

但是要调用该函数,前置条件和后置条件重新注册使用、堆栈设置等的约定是 C 函数的约定。C 和 C++ 函数具有不同的调用约定是合法的,并且可以这样做 - 例如 - 促进 C++ 异常处理。

请解释目标文件中的差异:函数名称与 C 语言链接和 C++ 语言链接。

  • 对于 C 链接,“f2”将是目标文件中的符号f2()
  • 对于 C++ 链接,“名为 f2 的函数不带参数”的一些损坏版本(对于 GNU,_Z2f2v它 demangles 为f2()

具有 C 语言链接和 C++ 语言链接的函数类型。

如上所述,这是关于在函数地址调用代码的寄存器/堆栈使用约定。此元信息不一定存储在对象的符号表信息中(当然也不是符号名称键本身的一部分)。

此外,由于每个函数都采用其中一种调用约定,编译器需要知道在遵循指向函数的指针时要使用的调用约定:有了这种见解,我认为问题中的其余代码变得清晰。

在http://developers.sun.com/solaris/articles/mixing.html有一个很好的讨论- 特别是我推荐使用指向函数的指针部分。

于 2011-05-20T02:41:05.463 回答
1

众所周知,C/C++ 代码翻译由两个主要阶段组成:编译和链接。当编译器生成目标文件时,它会将信息传递给链接器,指定在哪些目标文件中调用或引用给定函数。在 C 中就是这样,函数有一个名称和匹配的定义。

// file1.c
void foo(void) {}

并且编译后file1.obj存储了foo符号定义的代码和信息。

但是当 C++ 出现时,符号名称变得更加复杂。一个函数可以被重载或者是一个类的成员。但是链接器不想知道它。为了保持旧链接器的简单性和可重用性,它需要一个名称,无论 foo 是:

void foo(void) {}
void foo(int) {}
void ClassA::foo(void) {}

但它不能再被称为 foo 所以这里来了名字修饰。我们可能会从编译器中得到一些变体,例如 foo_void、foo_int、foo_void_classa。最后,链接器很高兴,因为所有这些看起来都像简单的符号。

当我们想在 C++ 代码中调用用 C 编译器编译的 foo 函数时,我们必须告诉编译器我们希望 foo 是 C 风格的 foo 而不是 C++ 编译器可能假设的 foo_void。它是使用以下方法完成的:

extern "C" void foo();

现在编译器知道 foo 是使用 C 编译器编译的,并将信息传递给该代码调用 foo 的链接器。链接器会将其与 file1.obj 中的 foo 定义相匹配。所以这就是我的想法。

其他一些指令,如 cdecl 或 stdcall 是 Windows 特定的,并告诉函数调用中的参数是如何传递的。是的,对于 C 和 C++,它是 cdecl。但是 Windows API 函数使用 stdcall - Pascal 约定(简单且历史上微软曾经在 Pascal 中提供 Windows 开发环境)。

于 2011-05-19T15:03:09.060 回答
0

每个函数、函数类型和对象都有一个语言链接,它被指定为一个简单的字符串。默认情况下,链接是“C++”。唯一的其他标准语言链接是“C”。所有其他语言链接和与不同语言链接关联的属性都是实现定义的。

于 2012-04-10T16:51:27.777 回答