Eng*_*999 133 c c++ name-mangling extern-c
我最近接受了一次采访,问了一个问题是extern "C"C++代码的用法是什么.我回答说它是在C++代码中使用C函数,因为C不使用名称修改.我被问到为什么C不使用名称错误,说实话我无法回答.
我知道当C++编译器编译函数时,它为函数提供了一个特殊的名称,主要是因为我们可以在C++中使用同名的重载函数,这些函数必须在编译时解析.在C中,函数的名称将保持不变,或者在它之前使用_.
我的疑问是:允许C++编译器破坏C函数有什么问题?我原以为编译器给它们的名称无关紧要.我们在C和C++中以相同的方式调用函数.
Sha*_*esh 187
上面有点回答,但我会尝试把事情放到上下文中.
首先,C首先出现.因此,C所做的就是"默认".它不会破坏名称,因为它不会.函数名称是函数名称.全球化是全球性的,依此类推.
然后C++出现了.C++希望能够使用与C相同的链接器,并且能够与用C编写的代码链接.但是C++不能保持C"mangling"(或者,缺少).看看以下示例:
int function(int a);
int function();
Run Code Online (Sandbox Code Playgroud)
在C++中,这些是不同的函数,具有不同的主体.如果它们都没有被破坏,则两者都将被称为"函数"(或"_function"),并且链接器将抱怨重新定义符号.C++解决方案是将参数类型转换为函数名称.因此,一个被调用_function_int而另一个被调用_function_void(不是实际的重整方案)并且避免了冲突.
现在我们遇到了问题.如果int function(int a)在C模块中定义,并且我们只是在C++代码中使用它的头(即声明)并使用它,编译器将生成一个指令供链接器导入_function_int.定义函数时,在C模块中,没有调用它.它被称为_function.这将导致链接器错误.
为了避免这个错误,在函数声明期间,我们告诉编译器它是一个被设计为与C编译器链接或编译的函数:
extern "C" int function(int a);
Run Code Online (Sandbox Code Playgroud)
C++编译器现在知道导入_function而不是_function_int,并且一切都很好.
unw*_*ind 45
并不是说他们"不能",一般而言他们不是.
如果你想在一个被调用的C库中调用一个函数foo(int x, const char *y),那么让你的C++编译器进入foo_I_cCP()(或者其他什么,只是在这里制作一个错误的方案)就好了,因为它可以.
该名称无法解析,该函数在C中,其名称不依赖于其参数类型列表.所以C++编译器必须知道这一点,并将该函数标记为C以避免进行修改.
请记住,所述C函数可能位于您没有源代码的库中,您所拥有的只是预编译的二进制文件和标题.所以你的C++编译器不能做"它自己的东西",它毕竟不能改变库里的东西.
Lig*_*ica 32
允许C++编译器破坏C函数有什么问题?
它们不再是C函数.
功能不仅仅是签名和定义; 函数如何工作很大程度上取决于调用约定等因素.指定在您的平台上使用的"应用程序二进制接口"描述了系统如何相互通信.系统使用的C++ ABI指定了名称修改方案,因此该系统上的程序知道如何调用库中的函数等等.(阅读C++ Itanium ABI就是一个很好的例子.你很快就会明白为什么它是必要的.)
这同样适用于您系统上的C ABI.有些C ABI实际上有一个名称修改方案(例如Visual Studio),因此对于某些功能而言,这不是关于"关闭名称修改"以及更多关于从C++ ABI切换到C ABI的问题.我们将C函数标记为C函数,C ABI(而不是C++ ABI)与之相关.声明必须与定义匹配(无论是在同一个项目中还是在某个第三方库中),否则声明是毫无意义的.没有它,你的系统根本不知道如何定位/调用这些功能.
至于为什么平台没有定义C和C++ ABI是相同的并且摆脱这个"问题",这是部分历史的 - 原始的C ABI对于C++是不够的,C++具有名称空间,类和运算符重载,所有其中需要以某种方式以符号计算机友好的方式表示符号的名称 - 但也有人认为使C程序现在遵守C++对C社区是不公平的,这将不得不忍受更复杂的ABI只是为了其他一些想要互操作性的人.
MSa*_*ers 19
事实上, MSVC以简单的方式编写了错误的C名称.它有时会附加@4或者是另一个小数字.这涉及调用约定和堆栈清理的需要.
所以前提是有缺陷的.
sup*_*cat 13
程序部分用C语言编写,部分用其他语言编写(通常是汇编语言,但有时候是Pascal,FORTRAN或其他语言).让程序包含由不同人员编写的不同组件也是很常见的,这些人可能没有所有内容的源代码.
在大多数平台上,有一个规范 - 通常称为ABI [应用程序二进制接口],它描述了编译器必须做什么来生成具有特定名称的函数,该函数接受某些特定类型的参数并返回某个特定类型的值.在某些情况下,ABI可能会定义多个"调用约定"; 这种系统的编译器通常提供一种指示哪种调用约定应该用于特定功能的方法.例如,在Macintosh上,大多数Toolbox例程都使用Pascal调用约定,因此像"LineTo"这样的原型将类似于:
/* Note that there are no underscores before the "pascal" keyword because
the Toolbox was written in the early 1980s, before the Standard and its
underscore convention were published */
pascal void LineTo(short x, short y);
Run Code Online (Sandbox Code Playgroud)
如果项目中的所有代码都是使用相同的编译器编译的,那么编译器为每个函数导出的名称无关紧要,但在许多情况下,C代码必须调用使用其他工具编译的函数.不能用目前的编译器重新编译[甚至可能不在C中].因此,能够定义链接器名称对于使用这些功能至关重要.
Sha*_*esh 12
我将添加另一个答案,以解决发生的一些切向讨论.
C ABI(应用程序二进制接口)最初调用以相反的顺序在堆栈上传递参数(即 - 从右向左推送),其中调用者还释放堆栈存储.现代ABI实际上使用寄存器来传递参数,但是许多重复考虑都会回到原始堆栈参数传递.
相比之下,最初的Pascal ABI将参数从左向右推,并且被调用者不得不弹出参数.最初的C ABI在两个重要方面优于原来的Pascal ABI.参数推送顺序意味着第一个参数的堆栈偏移始终是已知的,允许具有未知数量参数的函数,其中早期参数控制有多少其他参数(ala printf).
C ABI优越的第二种方式是在呼叫者和被呼叫者不同意有多少参数的情况下的行为.在C情况下,只要你实际上没有访问过去的参数,就不会发生任何不好的事情.在Pascal中,从堆栈中弹出错误数量的参数,并且整个堆栈已损坏.
最初的Windows 3.1 ABI基于Pascal.因此,它使用Pascal ABI(从左到右的顺序,被调用者弹出的参数).由于参数编号的任何不匹配都可能导致堆栈损坏,因此形成了一个错位方案.每个函数名都被修改了一个数字,表示其参数的大小(以字节为单位).那么,在16位机器上,以下函数(C语法):
int function(int a)
Run Code Online (Sandbox Code Playgroud)
被破坏了function@2,因为int是两个字节宽.这样做是为了如果声明和定义不匹配,链接器将无法在运行时找到该函数而不是损坏堆栈.相反,如果程序链接,那么您可以确保在调用结束时从堆栈中弹出正确的字节数.
32位Windows及其后使用stdcallABI.它类似于Pascal ABI,除了推送顺序在C中,从右到左.与Pascal ABI一样,名称mangling将参数字节大小变为函数名称以避免堆栈损坏.
与此处其他地方提出的声明不同,C ABI不会破坏函数名称,即使在Visual Studio上也是如此.相反,用stdcallABI规范修饰的修改函数并不是VS独有的.即使在编译Linux时,GCC也支持这种ABI.Wine广泛使用它,它使用它自己的加载器来允许Linux编译的二进制文件运行时链接到Windows编译的DLL.
C++编译器使用名称修改以允许重载函数的唯一符号名称,否则其签名将相同.它基本上对参数类型进行编码,这允许在基于函数的级别上进行多态.
C不需要这个,因为它不允许功能过载.
请注意,名称重整是一个(但肯定不是唯一的!)原因,人们不能依赖'C++ ABI'.
C++希望能够与链接它的C代码或它链接的C代码互操作.
C期望非名称错误的函数名称.
如果C++损坏它,它将无法从C中找到导出的非破坏函数,或者C将找不到C++导出的函数.C链接器必须获得它自己期望的名称,因为它不知道它来自或转向C++.