7

我今天遇到了一个奇怪的错误,直到现在我还是设法避免了它。

文件 1.cpp:

#include <iostream>
inline void print() { std::cout << "Print1\n"; }
void y() { print(); }

文件 2.cpp:

#include <iostream>
inline void print() { std::cout << "Print2\n"; }
void x() { print(); }

主.cpp:

int x();
int y();

int main(){
    x();
    y();
}

输出:

Print1                         (Expected Print2)
Print1

因为print()具有内联链接,所以不会产生多重定义错误(用 编译g++ -Wall file1.cpp file2.cpp main.cpp)并且重复的符号会被静默折叠。我看到的实际情况是使用内联类方法,而不是显式内联函数,但效果是一样的。

我想知道是否有链接器选项或类似选项可以让我在发生此类错误时发出警告?

4

4 回答 4

2

根据标准,没有必要/强制生成错误/警告。这违反了 C++ 的单一定义规则,因为它们具有不同的函数体。

引用自: 默认情况下的外部内联

“外部内联”的含义是,如果对函数的调用不是内联生成的,那么编译器应该只制作函数定义的一份副本,以便在所有目标文件之间共享。

如果发生上述情况,根据语言标准,该程序的行为被认为是未定义的,但编译器和链接器都不需要给出诊断消息。实际上,这意味着,根据实现的工作方式,编译器或链接器可能只是默默地选择要在任何地方使用的定义之一。

该行为与 GCC 在此处对模糊链接的定义一致。

于 2013-02-22T20:14:13.467 回答
2

这些函数不是内部链接或匿名命名空间,因此它们显然是外部的,具有相同的名称。他们有不同的身体,所以你显然违反了单一定义规则。那时任何关于会发生什么的猜测都没有用,因为您的程序格式不正确。

我猜你编译时没有优化,编译器生成了函数调用而不是实际内联,它选择了一个主体作为要调用的函数(让另一个成为孤立的)。现在大概如果您使用优化进行编译,您的预期输出将被发出,但您的程序仍然不正确。

编辑评论:不幸的是,编译器不需要诊断违反一个定义规则的情况(他们甚至可能无法检测到所有情况)。但是,您可以做一些事情:

  • 使源私有函数始终在匿名命名空间中static或在匿名命名空间中(出于逻辑分组目的,我更喜欢命名空间,但两者都可以)。
  • 特别注意inline方法(无论位置如何),因为它们明确告诉编译器所有版本都是相同的(对于非内联方法,您很可能至少会从链接器获得重复符号错误)。这里最安全的方法是避免使用全局命名空间内联函数(或在命名时特别注意)。此外,您需要非常小心 changed #defines 不会更改函数体(例如assert,在一个使用有NDEBUG而另一个没有使用的内联函数中使用)。
  • 逻辑上使用类和命名空间来分解代码并帮助防止同一符号的多个定义。
于 2013-02-22T20:16:12.530 回答
0

我将从我在这里找到的类似主题的电子邮件线程中无耻地复制粘贴......

这不是 gcc 问题。

您没有提到您使用的是哪个操作系统。我假设它是 GNU/Linux。在 GNU/Linux 或任何其他基于 ELF 的系统上,有一个全局变量和函数的全局命名空间。当两个共享库使用相同的全局变量名时,它们指的是同一个变量。这是一个特点。

如果您希望发生不同的事情,请查看符号可见性和链接器版本脚本。

似乎您应该能够告诉链接器/编译器符号 X 应该是唯一的,如果不是,则抱怨。

于 2013-02-22T20:13:42.130 回答
-2

要将函数设为 .cpp(或 .c)文件的本地函数,您必须将该函数设为static. 当您在两个单独的 .cpp 文件中声明 print 时,它们都编译为print_v,第二个符号被编译器删除(忽略)*。

这只是对它如何在我的机器上起作用的假设。我在 MAC OS X Mountain Lion 上尝试了相同的代码,得到了与您相同的结果,但我能够通过添加static这两个函数来解决它。

* 如果你在编译中改变文件的顺序,行为就会改变。尝试g++ file{2,1}.cpp main.cpp,输出应该是

print2

print2

此致, 阿卜杜拉赫曼·阿洛泰比

于 2013-02-22T23:55:30.207 回答