我最近接受了一次采访,问了一个问题是在{+ 1}}中C ++代码的用法是什么。我回答说它是在C ++代码中使用C函数,因为C不使用名称修改。我被问到为什么C不会使用名称错误,说实话我无法回答。
据我所知,当C ++编译器编译函数时,它为函数提供了一个特殊的名称,主要是因为我们可以在C ++中重载具有相同名称的函数,这些函数必须在编译时解析。在C中,函数的名称将保持不变,或者在它之前使用_。
我的疑问是:允许C ++编译器破坏C函数的错误是什么?我原以为编译器赋予它们的名称并不重要。我们在C和C ++中以相同的方式调用函数。
答案 0 :(得分:189)
上面有点回答,但我会尝试把事情放到上下文中。
首先,C首先出现。因此,C所做的就是“默认”。它不会破坏名称,因为它不会。函数名称是函数名称。全球化是全球性的,依此类推。
然后C ++出现了。 C ++希望能够使用与C相同的链接器,并且能够与用C编写的代码链接。但是C ++不能保留C“mangling”(或者,缺少)。请查看以下示例:
int function(int a);
int function();
在C ++中,这些是不同的功能,具有不同的主体。如果它们都没有被破坏,则两者都将被称为“函数”(或“_function”),并且链接器将抱怨重新定义符号。 C ++解决方案是将参数类型转换为函数名称。因此,一个被称为_function_int
,另一个被称为_function_void
(不是实际的重整方案),并且避免了碰撞。
现在我们遇到了问题。如果在C模块中定义了int function(int a)
,并且我们只是在C ++代码中使用它的标题(即声明)并使用它,则编译器将生成一条指令,以便链接器导入_function_int
。定义函数时,在C模块中,没有调用它。它被称为_function
。这将导致链接器错误。
为避免该错误,在函数的声明期间,我们告诉编译器它是一个旨在与C编译器链接或编译的函数:
extern "C" int function(int a);
C ++编译器现在知道要导入_function
而不是_function_int
,一切都很好。
答案 1 :(得分:45)
一般情况下,他们不能"他们不是。
如果你想在一个名为foo(int x, const char *y)
的C库中调用一个函数,那么让你的C ++编译器变成foo_I_cCP()
(或者其他什么,只是组成一个错误的方案)是不行的。在这里当场)因为它可以。
该名称无法解析,该函数在C中,其名称不依赖于其参数类型列表。所以C ++编译器必须知道这一点,并将该函数标记为C以避免进行修改。
请记住,所说的C函数可能位于您不具备源代码的库中,您所拥有的只是预编译的二进制文件和标题。所以你的C ++编译器不能做自己的事情"它毕竟不能改变库中的内容。
答案 2 :(得分:32)
允许C ++编译器破坏C函数的错误是什么?
他们不再是C函数了。
功能不仅仅是签名和定义;函数如何工作很大程度上取决于调用约定等因素。 "应用程序二进制接口"指定在您的平台上使用的内容描述了系统如何相互通信。系统使用的C ++ ABI指定了名称重整方案,因此该系统上的程序知道如何调用库中的函数等等。 (阅读C ++ Itanium ABI就是一个很好的例子。你很快就会明白为什么它是必要的。)
这同样适用于您系统上的C ABI。有些C ABI实际上有一个名称修改方案(例如Visual Studio),所以这不是关于"关闭名称修改和#34;有关某些功能的更多信息,请参阅从C ++ ABI切换到C ABI。我们将C函数标记为C函数,C ABI(而不是C ++ ABI)与之相关。声明必须与定义匹配(无论是在同一个项目中还是在某个第三方库中),否则声明是毫无意义的。 没有它,您的系统只是不知道如何定位/调用这些功能。
至于为什么平台不能定义C和C ++ ABI是相同的并且摆脱这个"问题"那是部分历史的 - 原始的C ABI不是&#39 ;对于C ++来说已经足够了,它有名称空间,类和运算符重载,所有这些都需要以某种方式以符合计算机的方式用符号的名称表示 - 但是人们也可能认为现在使C程序遵守C ++对C社区是不公平的,为了其他想要互操作性的人,C社区必须忍受大规模复杂的ABI。
答案 3 :(得分:19)
@4
或其他小数字。这涉及调用约定和堆栈清理的需要。
所以前提是有缺陷的。
答案 4 :(得分: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);
如果项目中的所有代码都是使用相同的编译器编译的,那么 无论编译器为每个函数导出的名称是什么,但在 在许多情况下,C代码需要调用函数 使用其他工具编译,无法使用当前编译器重新编译 [甚至很可能不在C]。能够定义链接器名称 因此对于使用这些功能至关重要。
答案 5 :(得分:12)
我将添加另一个答案,以解决发生的一些相关讨论。
C ABI(应用程序二进制接口)最初调用以相反的顺序在堆栈上传递参数(即 - 从右向左推送),其中调用者还释放堆栈存储。现代ABI实际上使用寄存器来传递参数,但是许多重复的考虑都会回到原始的堆栈参数传递。
相比之下,最初的Pascal ABI将参数从左向右推,并且被调用者不得不弹出参数。最初的C ABI在两个重要方面优于原来的Pascal ABI。参数push顺序意味着第一个参数的堆栈偏移始终是已知的,允许具有未知数量参数的函数,其中早期参数控制有多少其他参数(ala printf
)。
C ABI优越的第二种方式是在调用者和被调用者不同意有多少参数的情况下的行为。在C情况下,只要你没有实际访问过去的参数,就不会发生任何不好的事情。在Pascal中,从堆栈中弹出错误数量的参数,并且整个堆栈已损坏。
最初的Windows 3.1 ABI基于Pascal。因此,它使用Pascal ABI(从左到右的顺序,被调用者弹出的参数)。由于参数编号的任何不匹配都可能导致堆栈损坏,因此形成了一个错位方案。每个函数名都被修改了一个数字,表示其参数的大小(以字节为单位)。因此,在16位机器上,以下函数(C语法):
int function(int a)
被损坏为function@2
,因为int
是两个字节宽。这样做是为了如果声明和定义不匹配,链接器将无法在运行时找到该函数而不是损坏堆栈。相反,如果程序链接,那么您可以确保在通话结束时从堆栈中弹出正确的字节数。
32位Windows及其后使用stdcall
ABI代替。它类似于Pascal ABI,除了推送顺序在C中,从右到左。与Pascal ABI一样,名称mangling将参数字节大小修改为函数名称以避免堆栈损坏。
与此处其他地方提出的声明不同,即使在Visual Studio上,C ABI也不会破坏函数名称。相反,用stdcall
ABI规范修饰的修剪函数对于VS来说并不是唯一的。即使在编译Linux时,GCC也支持这种ABI。 Wine广泛使用它,它使用它自己的加载器来允许将Linux编译的二进制文件运行时链接到Windows编译的DLL。
答案 6 :(得分:9)
C ++编译器使用名称修改以允许重载函数的唯一符号名称,否则其签名将相同。它基本上对参数类型进行编码,允许在基于函数的级别上进行多态化。
C不需要这个,因为它不允许函数重载。
请注意,名称重整是一个(但肯定不是唯一的!)原因,一个人不能依赖'C ++ ABI'。
答案 7 :(得分:8)
C ++希望能够与链接它的C代码或它链接的C代码互操作。
C期望非名称错误的函数名称。
如果C ++损坏它,它将无法从C中找到导出的非破坏函数,或者C将找不到C ++导出的函数。 C链接器必须得到它自己期望的名称,因为它不知道它来自或转向C ++。
答案 8 :(得分:3)
管理C函数和变量的名称将允许在链接时检查它们的类型。目前,所有(?)C实现都允许您在一个文件中定义变量,并将其作为另一个文件中的函数调用。或者您可以使用错误的签名声明一个函数(例如void fopen(double)
,然后调用它。
我在1991年通过使用修改来提出a scheme for the type-safe linkage of C variables and functions。该方案从未被采用,因为正如其他人在此处所指出的那样,这会破坏向后兼容性。