一个名字或types是否意味着某种语言的联系是什么意思?
根据(c)ANSI ISO / IEC 14882:2003,第127页:
联动规格嵌套。 当联动规范嵌套时,最内层的决定语言。 联动规范不build立范围。 链接规范只能在命名空间范围(3.3)中出现。 在一个链接规范中,指定的语言链接适用于由声明引入的所有函数声明符,函数名称和variables名称的函数types。
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 ++语言连接的函数types。
语言链接是用于C++
和non-C++
代码片段之间的链接的术语。 通常,在C ++程序中,所有函数名称,函数types甚至variables名称都具有默认的C ++语言链接。
一个C ++对象代码可以链接到另一个使用其他源语言(如C
)使用预定义的链接说明符生成的另一个对象代码。
正如你必须了解的name mangling
的概念,它编码函数名称,函数types和variables名称,以便为它们生成一个唯一的名称。 这允许链接器区分常用名称(如在函数重载的情况下)。 链接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 ++函数,因为它缺less一个链接指定符。
第2行因此声明foo是一个C函数,它接受一个指向C ++函数的指针。
第5行尝试使用指向g的指针调用foo,这是一个C函数,types不匹配。
函数名称链接中的差异:
我们来看两个不同的文件:
一个用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
使用外部“C”连接,没有名称为函数foo
。 所以任何使用它的程序(假设我们共享一个lib)都可以直接调用foo(带有辅助函数,如dlsym
和dlopen
),而不考虑任何名称的混乱效果。
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
另一方面,当没有使用extern "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
“名称f2具有C ++语言链接”在C ++语言链接中,不仅函数的名称定义了它,而且还包括它的参数types和返回值。 在这种情况下,你有:void f2(void); 但你可以用它来定义:void f2(int a); 没有冲突,因为链接会将它们视为不同的types,这是C语言无法做到的事情。
“function的types有C语言连接”我不知道细节,但我知道它的高层次。 基本上它使一个C编译的函数从C链接。如果我没有记错在C和C ++中传递参数的方式是不同的。 在这种情况下,函数f2会像C编译器那样传递参数。 这样这个函数就可以连接到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
FUNC
这个名字是用“C”连接声明的,因为它在第一行说了extern "C"
。
名称f2
有C ++连接,因为这是默认的,在第二行没有其他连接。
名称f2
用于引用具有C链接function的事实不会更改名称的链接。
它与程序的ABI(应用程序二进制接口)有关。
由于API指定了程序源代码的外部接口,所以ABI指定程序的二进制代码 (编译版本)的外部接口。
C函数原本只有几种不同的forms。 就像是
int foo(int);
将由编译器以下划线作为前缀,形成_foo
,然后导出为可用于其他应用程序。
但是,这还不够。 例如,如果您查看Windows API,则会看到如下所示的内容:
DWORD CreateWindowW(...); //Original parameters DWORD CreateWindowExW(..., ...); //More parameters
这是因为无法通过查看函数的名称来区分函数的重载,所以人们开始通过添加Ex
后缀(或类似的)来改变它们。
这变得相当丑陋,它仍然不允许运算符重载,这是在C ++中的特色。 正因为如此,C ++想出了名字混搭 ,把额外的信息放在函数的名字里,比如它的参数的数据types,并且用很多的@
符号使它变得含糊不清。
这一切都很好,除了它没有完全标准化 。
当然,随着新的语言和编译器的出现,每个人都提出了自己的计划,有些计划与其他计划不兼容。 因此,如果您需要导入或导出外部函数,则需要指定编译器应该查找哪种types的ABI,因此需要指定extern "C++"
。
这是什么意思呢? 例如,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()
你会在对象文件中find它的名字aka symbol,这将是一个“不带参数的名为f2的函数”的破坏版本。 你可以通过编译上面的代码并检查对象来validation这一点(例如w / GNU tools nm --demangle
)。
但是为了调用函数,前后条件的重新注册使用,堆栈设置等约定是C函数的约定。 C和C ++函数具有不同的调用约定是合法的,并且可以完成 – 例如,以促进C ++exception处理。
请解释目标文件的不同之处:用C语言链接和C ++语言链接的函数名称。
- 对于C链接,“f2”将是由
f2()
产生的对象文件中的符号, - 对于C ++的链接,一些名为“f2不带参数的函数”(对于GNU来说,
_Z2f2v
f2()
)
C语言连接和C ++语言连接的函数types。
如上所述,这是关于在函数地址调用代码的寄存器/堆栈使用约定。 这个元信息不一定存储在对象的符号表信息中(当然不是符号名称密钥本身的一部分)。
而且,因为每个函数都采用了其中一个调用约定,编译器需要知道下一个函数指针时要使用的调用约定:有了这种认识,我认为问题中剩余的代码就变得清晰了。
在http://developers.sun.com/solaris/articles/mixing.html有一个很好的讨论; – 特别是我build议使用function指针部分。
众所周知,在C / C ++中,代码翻译由两个主要阶段组成:编译和链接。 当编译器生成目标文件时,它将信息传递给链接器,指定给定函数的哪个目标文件被调用或引用。 在C中就是这样,函数有一个名字和匹配的定义。
// file1.c void foo(void) {}
编译完成后,file1.obj将存储关于foo符号定义的代码和信息。
但是当C ++进入时,符号名称变得更加复杂。 函数可能被重载或是类的成员。 但链接器不想知道它。 为了保持旧连接器的简单性和可重用性,它需要一个名称,而不pipefoo是:
void foo(void) {} void foo(int) {} void ClassA::foo(void) {}
但它不能被称为只是foo了所以这里来的名字mangling。 我们可以从编译器中获得一些变体,如foo_void,foo_int,foo_void_classa。 最后,链接器很快乐,因为所有这些看起来像简单的符号。
当我们想用C ++编译器调用用C编译器编译的foo函数时,我们必须告诉编译器,我们希望foo是C风格的foo而不是foo_void,因为C ++编译器可能会假设。 它使用:
extern "C" void foo();
现在编译器知道foo是使用C编译器编译的,并将信息传递给此代码调用foo的链接器。 链接器将它与file1.obj中的foo定义相匹配。 所以我就这么想
一些其他指令,如cdecl或stdcall是Windows特定的,并告诉函数调用中的参数如何传递。 是的,对于C和C ++,它是cdecl。 但Windows API函数使用stdcall – Pascal约定(简单,历史上,Microsoft曾经在Pascal中提供过Windows开发环境)。
每个函数,函数types和对象都有一个语言链接,它被指定为一个简单的string。 默认情况下,链接是“C ++”。 唯一的其他标准语言链接是“C”。 所有其他语言链接和与不同语言链接相关的属性都是实现定义的。