好的,我有3个文件,一个标题,所述标题的来源和一个主要文件。在头文件中,我定义了一个带有函数的类。在源文件中,我定义了该函数。但是,在主文件中,我重新定义函数,然后在main函数中创建类的实例并调用该函数。编译得很好 - 没有任何警告。至少可以说输出是可怕的。
标题:testme.h
#ifndef testme_h_
#include <iostream>
using namespace std;
class wtf {
public:
string getStr();
};
#endif
来源:testme.cpp
#include "testme.h"
string wtf::getStr() {
return "Hello World!";
};
Main:main.cpp
#include <iostream>
using namespace std;
#include "testme.h"
string wtf::getStr()
{
return "God is Dead.";
}
int main()
{
wtf f;
cout << f.getStr() << endl;
}
输出:
God is Dead.
为什么这样做?为什么没有关于多个定义的错误?为什么源文件的定义会被忽略?为什么没有警告?
部分答案 当它被重新编译为&#34; g ++ main.cpp testme.cpp -o sanity.o&#34;事实上,它确实会产生链接器错误。
然而,我得到的这个小案例反映了我在一个更大的程序中遇到的问题,该程序具有在库中定义的函数,但我们在另一个&#34;测试套件中重新定义了一个函数&#34;程序大致相同。为什么会这样?它在库中的情况如何允许它覆盖ODR?
答案 0 :(得分:0)
为什么这样做?为什么多个定义没有错误?为什么源文件的定义会被忽略?为什么没有警告?
部分答案当重新编译为“g ++ main.cpp testme.cpp -o sanity.o”时,它确实会产生链接器错误。
在 C ++的设计和演变中,Stroustrup说,在设计一个特性时,他偶尔可以选择(1)编译器能够强制执行警告和错误消息的复杂规则。 ,或(2)编译器可能无法在所有情况下强制执行的简单规则。他试图选择简单的规则,他希望编译器最终能够执行/检测错误。 C和C ++的几个部分有效地展示了20世纪70年代,80年代,90年代等的艺术水平(例如,inline
,register
,volatile
)。可能会有更多。
粗略地说,这种权衡是标准中未定义行为的来源。如果无法强制执行简单规则(或委员会认为强制执行该规则很昂贵),则规则将保留在标准中,并且违规将被声明为未定义的行为。 It is the programmer's responsibility to never trigger undefined behavior。编译器和相关工具有可能在某些时候强制执行其中一些规则。某些编译器或相关工具甚至可以一直强制执行其中一些规则。但是,一般来说,你是独立的。
设计和演变甚至包括对您的具体问题的讨论。在编写编译器之后,Stroustrup也不想编写链接器。他提出了一种依赖系统链接器的简单方法,但在早期,许多连接器对符号的长度有严格的限制。他努力说服编写系统链接器的人改变它们以使它们与Cfront一起工作。最终,他成功了。
今天,链接器通常附带编译器,但标准委员会仍将其视为一个单独的工具,可能不在编写编译器的人的控制之下。一个定义规则主要用于确保编译器的输出与大多数标准链接器兼容,但委员会不要求这些链接器能够检测到ODR的违规。此外,程序可以逐步编译和链接,因此不能保证链接器有足够的信息来检测违反ODR的行为。
值得注意的是many compilers将为inline
函数和template
函数生成多个符号,期望链接器抛出冗余定义。因此,链接器看到多个定义并不一定是错误。 (就ODR而言,这些定义必须相同,以便链接器可以丢弃除一个以外的所有定义。)
它在库中的含义如何允许它覆盖ODR?
我不会说“覆盖”。听起来这种行为是故意的。我会说“没有被发现就违反了。”答案很简单“标准委员会不希望链接器始终检测到违规行为,许多链接器不会。”避免ODR违规是您的责任。但是,您不是自己的:名称空间部分存在部分是为了使这个问题更容易解决。