为什么C函数不能被命名?

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,并且一切都很好.

  • @mucaho:你在谈论源代码的可移植性/兼容性.即API.Voo正在讨论*binary*兼容性,没有重新编译.这需要**ABI兼容性**.C++编译器定期更改版本之间的ABI.(例如,g ++甚至没有尝试使用稳定的ABI.我认为他们不会为了娱乐而打破ABI,但是当有什么东西可以获得并且没有其他好的方法时,他们不会避免需要ABI更改的更改去做吧.). (13认同)
  • @ Engineer999曾经想知道为什么没有可移植的C++库这样的东西,但它们要么准确指定你必须使用的编译器(和标准库)的版本(和标志),要么只导出一个C API?你去吧 C++几乎是有史以来发明的最不可移植的语言,而C恰恰相反.在这方面有一些努力,但是现在如果你想要一些真正便携的东西,你会坚持使用C. (5认同)
  • 每个编译器都以自己特殊的方式来完成。如果您使用相同的编译器编译所有内容,则无关紧要。但是,如果您尝试使用使用 Borland 编译器编译的库,或者使用 Microsoft 编译器构建的程序,那么……祝您好运;你会需要它:) (2认同)

unw*_*ind 45

并不是说他们"不能",一般而言他们不是.

如果你想在一个被调用的C库中调用一个函数foo(int x, const char *y),那么让你的C++编译器进入foo_I_cCP()(或者其他什么,只是在这里制作一个错误的方案)就好了,因为它可以.

该名称无法解析,该函数在C中,其名称不依赖于其参数类型列表.所以C++编译器必须知道这一点,并将该函数标记为C以避免进行修改.

请记住,所述C函数可能位于您没有源代码的库中,您所拥有的只是预编译的二进制文件和标题.所以你的C++编译器不能做"它自己的东西",它毕竟不能改变库里的东西.

  • @ Engineer999:如何为定义命名,为声明设置另一个名称?_"有一个名为Brian的功能,你可以打电话." "好的,我会打电话给布莱恩." "抱歉,没有一个名为Brian的功能."_原来它被称为格雷厄姆. (13认同)

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只是为了其他一些想要互操作性的人.

  • @vaxquis:是的,不是"C++的ABI",而是"C++ ABI",就像我有一个"房子钥匙"一样,不适用于每个房子.猜猜它可能更清楚,尽管我试图通过开始使用短语_"你的系统使用的C++ ABI**"来尽可能清楚.为了简洁起见,我在后来的话语中放弃了澄清器,但我会接受一个可以减少混乱的编辑! (3认同)
  • `+ int(PI/3)`,但有一粒盐:我会非常谨慎地谈论"C++ ABI"...... AFAIK,有*尝试*来定义C++ ABI,但没有**真实***事实上*/*de jure*标准 - 如https://isocpp.org/files/papers/n4028.pdf所述(并且我完全同意),引用,*具有讽刺意味的是,C++实际上始终支持一种通过extern"C"求助于C++的C子集来发布具有稳定二进制ABI的API的方法.`C++ Itanium ABI`就是这样 - *某些*C++ ABI for Itanium ...正如http://stackoverflow.com/questions/7492180/c-abi-issues-list中所讨论的那样 (2认同)
  • AIUI C abi 往往是平台的属性,而 C++ ABI 往往是单个编译器的属性,甚至常常是编译器的单个版本的属性。因此,如果您想在使用不同供应商工具构建的模块之间进行链接,则必须使用 C abi 作为接口。 (2认同)

MSa*_*ers 19

事实上, MSVC以简单的方式编写了错误的C名称.它有时会附加@4或者是另一个小数字.这涉及调用约定和堆栈清理的需要.

所以前提是有缺陷的.

  • @Peter:字面意思相同. (12认同)
  • @Frankie_C:任何C标准都没有指定"Caller清理堆栈":从语言的角度来看,调用约定都不比另一个更标准. (5认同)
  • 这不是名字错误.它只是供应商特定的命名(或名称装饰)约定,以防止可执行文件链接到使用具有不同调用约定的函数构建的DLL的问题. (2认同)
  • 先用'_`做什么? (2认同)
  • 从MSVC的角度来看,"标准调用约定"就是你从`/ Gd,/ Gr,/ Gv,/ Gz`中选择的.(也就是说,除非函数声明明确指定调用约定,否则标准调用约定就是使用的.).您正在考虑`__cdecl`这是默认的标准调用约定. (2认同)

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中].因此,能够定义链接器名称对于使用这些功能至关重要.

  • @ user34660:不是qutie.这就是为什么C不能强制存在的功能,这些功能的实现需要修改可导出的名称,或允许存在多个同名符号,这些符号由次要特征区分. (2认同)

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.


OnM*_*uck 9

C++编译器使用名称修改以允许重载函数的唯一符号名称,否则其签名将相同.它基本上对参数类型进行编码,这允许在基于函数的级别上进行多态.

C不需要这个,因为它不允许功能过载.

请注意,名称重整是一个(但肯定不是唯一的!)原因,人们不能依赖'C++ ABI'.


Yak*_*ont 8

C++希望能够与链接它的C代码或它链接的C代码互操作.

C期望非名称错误的函数名称.

如果C++损坏它,它将无法从C中找到导出的非破坏函数,或者C将找不到C++导出的函数.C链接器必须获得它自己期望的名称,因为它不知道它来自或转向C++.