C++ 在与内联链接冲突的定义上生成错误

C++ 在与内联链接冲突的定义上生成错误,c++,linker,inline,C++,Linker,Inline,今天我遇到了一只奇怪的虫子,直到现在我还是设法避免了它 file1.cpp: #include <iostream> inline void print() { std::cout << "Print1\n"; } void y() { print(); } #include <iostream> inline void print() { std::cout << "Print2\n"; } void x() { print(); } int

今天我遇到了一只奇怪的虫子,直到现在我还是设法避免了它

file1.cpp:

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

int main(){
    x();
    y();
}
Print1                         (Expected Print2)
Print1
输出:

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

int main(){
    x();
    y();
}
Print1                         (Expected Print2)
Print1
由于
print()
具有内联链接,因此不会产生多定义错误(使用
g++-Wall file1.cpp file2.cpp main.cpp编译),重复符号会自动折叠。我看到的实际情况是使用内联类方法,而不是显式内联函数,但效果相同


我想知道是否有一个链接器选项或类似的,这将允许我产生一个警告时,这种类型的错误

我将从我在一个类似主题上找到的一个电子邮件线程中进行无耻的复制粘贴

这不是gcc的问题

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

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


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

根据标准,没有必要/强制要求生成错误/警告。这是对C++的违反,因为它们有不同的函数体

引述自:

“extern inline”的意思是,如果对函数的调用不正确,则 生成内联,那么编译器应该只复制 要在所有对象文件中共享的函数定义

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


该行为与GCC在此处的定义一致。

函数不是内部链接或匿名名称空间,因此它们显然是外部同名函数。他们有不同的身体,所以你显然违反了一个定义规则。在这一点上,任何关于将会发生什么的推测都是无用的,因为您的程序是错误的

我猜您是在没有优化的情况下编译的,编译器生成了函数调用,而不是实际的内联,并且它选择了一个实体作为要调用的函数(另一个是孤立的)。现在,假设您在启用优化的情况下编译,您的预期输出将被发出,但您的程序仍然不正确

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

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

要使函数成为.cpp(或.c)文件的本地函数,必须将该函数设置为
静态
。当您在两个单独的.cpp文件中声明打印时,编译器会删除(忽略)*第二个符号,这两个文件都编译为
print\v

这只是一个假设,它是如何在我的机器上运行的。我在MAC OS X mountain Lion上尝试了相同的代码,得到了与您相同的结果,但通过向这两个函数添加
static
,我解决了这个问题

*如果在编译过程中更改文件的顺序,则行为将发生更改。尝试
g++文件{2,1}.cpp main.cpp
,输出应该是

print2

print2

真诚地,
Abdulrahman Alotaibi

我认为它不会打印
Print1 Print1
。它也应该打印
Print2
。谢谢。回顾过去,我本应该更好地表述这个问题,但要澄清的是——我意识到程序违反了规定,gcc的行为是正确的——我只是对(可能是特定于工具链的)在编译时检测它的方法感兴趣。是的——这没有经过优化。我想这将是你所描述的优化启用(至少与这些小功能)。我意识到程序的格式不正确,gcc的行为是正确的——只是对检测/防止这种情况的方法感兴趣。