使用C链接的函数重载在C++中是如何工作的?

3

考虑这个例子:

int foo(void);
extern "C" int foo(void);

int main()
{
    return foo();
}

出现错误:

$ g++ -c main.cpp
main.cpp:2:16: error: conflicting declaration of ‘int foo()’ with ‘C’ linkage
    2 | extern "C" int foo(void);
      |                ^~~
main.cpp:1:5: note: previous declaration with ‘C++’ linkage
    1 | int foo(void);
      |     ^~~

这完全没有问题。

但是我们让前两行互换一下:

extern "C" int foo(void);
int foo(void);

int main()
{
    return foo();
}

现在它已经编译通过了,但是选择了C链接方式, 尽管C++链接方式是最后被找到的。

问题:

  1. 为什么情况2能够编译而情况1失败?我期望它们的行为应该是相同的。
  2. 对于情况2,为什么选择了C链接方式?是否可以更改它?

背景: 我有一个C++程序,其中函数名称偶尔与C标准库中的函数名称冲突。在这种情况下,我期望会出现错误,但它却编译通过了,并且选择了错误的链接方式。我需要找到一种方法使它能够始终失败(以修复所有这样的情况),或者强制选择C++链接方式来解决所有冲突函数的问题。


6
背景:我有一个C++程序,其中函数名称偶尔会与C标准库中的一个函数名称冲突。那段代码中有多少个using namespace std;语句?我强烈怀疑这里适用:为什么“using namespace std;”被认为是不好的实践?需要翻译的内容已经被翻译成了中文并且没有问题,以下是英文原文的答案:There is no way to know how many using namespace std; lines are in the code without actually looking at the code. The link provided is relevant as it discusses the potential issues with using using namespace std; in C++ programs. - Andrew Henle
1
C标准库中的名称在全局命名空间范围内是保留的。只能在自己的命名空间内使用它们。 (或者说,编写C++代码时永远不要使用全局命名空间,除了operator new/operator delete之外)。 - user17732522
@AndrewHenle 很不幸,即使只包含C++标准库头文件,也可能会将C标准库声明导入到全局命名空间范围内。因此,如果没有使用using namespace std;和任何.h C标准库头文件,问题很可能仍然存在。 - user17732522
@user17732522 同意 - 这里真正的问题是“函数名有时会与C标准库中的函数名冲突”。这远远超出了"代码异味"的范畴,我认为直接跳进了“恶臭代码污水沟”的领域 - “有时”意味着“有时你从libc调用strstr(),有时你从libMyLib调用strstr(),而你永远不知道你真正得到的是哪一个”,这对我来说是一种产生非常奇怪的海森堡错误的方法。 - Andrew Henle
该项目最初是一个独立的环境,因此冲突从未成为问题。但突然间你包含了一些stdlib的头文件,然后一切都停止工作了。 - stsp
@stsp 看来我记错了标准。C标准库名称通常不是保留的。它们仅保留用于C链接,仅其具体签名保留用于C++链接。因此,允许使用C++链接重载C。我仍然会遵循以前的建议。 - user17732522
1个回答

7
extern "C" int foo(void);
int foo(void);

这里使用 extern "C" 声明了 foo。重新声明并没有指定不同的调用约定,所以没有问题。

但是,

int foo(void);
extern "C" int foo(void);

这里第一行没有明确指定调用约定,所以默认选择了C++。第二行显式地指定了不同的调用约定,导致冲突。

那么问题2...在第一个声明设置了调用约定后无法更改。


如何解决这个问题...为你的C++函数使用不同的名称。或者将其放入命名空间中。或者将冲突的C++名称隔离到一个单独的.cpp文件中,并导出一个不同的函数(或只是函数指针)以便在其他地方进行调用(请参见下面的最后一段)。

另一个解决方法是注意#include的顺序,使得C++函数总是首先声明。这方面没有“好”的解决方法,除非对include的顺序严格要求。

唯一正确、有效的解决方法是不要有任何与C++标准库中的任何符号冲突。这是C++标准不允许的。重命名您自己的函数,或将它们放在您自己的命名空间中。


一个重要的细节是,链接器为这些函数生成的符号是不同的。C++对符号名称进行编译以启用命名空间、方法和重载。因此,在链接/运行的程序中,这两个函数都可以存在并被调用。问题只存在于源代码层面。


@user17732522添加了一个关于此事的加粗段落。 - hyde
1
该项目最初是一个独立的环境,因此冲突从未成为问题。但一旦包含一些C头文件,工作代码就会默默地中断。在包含Cish之前包含C++头文件,或者使用extern "C++"可能有效,但不起作用,因为C函数具有例如“int”参数,而C++函数具有“short”参数。因此它们仍然不冲突。:( 在存在具有C链接的函数的情况下禁止任何重载将是很好的,但我不认为这是可能的。 - stsp
1
@hyde 对于我之前的评论感到抱歉。事实证明,我记错了标准。似乎这些名称仅保留给具有C链接的函数,并且只有特定签名被保留用于两种链接规范。此外,根据https://cplusplus.github.io/CWG/issues/1708.html,现在允许将C链接与C ++链接重载,但是我对https://eel.is/c++draft/dcl.link#7.sentence-1中当前措辞有点困惑。 - user17732522
@stsp 在重新阅读标准后,我意识到确实允许像那样用不同的语言链接进行重载。但如果您的问题真的是例如重载决议选择了错误的重载,那么这首先不是链接的问题,而只是一种名称冲突,就像您在使用任何库(无论是 C 还是 C++)时可能会遇到的情况一样。而且解决方案很简单,只需在自己的命名空间中工作即可。(这适用于库以及用户代码,但不幸的是 C 库无法遵守该规则,因为 C 没有命名空间。) - user17732522
我最终进行了手动重命名,谢谢。但我仍然感到非常失望的是,没有办法至少让冲突变得明显,因为我甚至不能确定我是否修复了所有实例。 - stsp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接