-2

好的,我有 3 个文件、一个标头、一个标头的来源和一个主文件。在头文件中,我定义了一个带有函数的类。在源文件中,我定义了函数。但是,在主文件中,我重新定义了函数,然后在主函数中创建类的实例并调用该函数。这编译得很好 - 没有任何警告。至少可以说输出是可怕的。

头文件: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.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.

为什么这行得通?为什么没有关于多个定义的错误?为什么源文件的定义会被忽略?为什么没有警告?

部分答案 当它被重新编译为“g++ main.cpp testme.cpp -o sanity.o”时,实际上会产生链接器错误。

然而,让我明白的是,我写的这个小案例反映了我在一个更大的程序中遇到的一个问题,该程序在库中定义了一个函数,但是我们以几乎相同的方式在另一个“测试套件”程序中重新定义了一个函数。为什么会这样?它在库中允许它覆盖 ODR 怎么样?

4

1 回答 1

0

为什么这行得通?为什么没有关于多个定义的错误?为什么源文件的定义会被忽略?为什么没有警告?

部分答案 当它被重新编译为“g++ main.cpp testme.cpp -o sanity.o”时,实际上会产生链接器错误。

C++ 的设计和演进中,Stroustrup 说,在设计一个特性时,他偶尔会在(1)编译器能够通过警告和错误消息强制执行的复杂规则,或者(2)一个简单的规则,即编译器可能无法在所有情况下强制执行。他试图选择简单的规则,并希望编译器最终能够强制/检测错误。C 和 C++ 的几个部分有效地展示了 1970 年代、1980 年代、1990 年代等的最新技术(例如inline,、、、registervolatile。本来可以更多。

粗略地说,这种权衡是标准中未定义行为的根源。如果一条简单的规则无法执行(或委员会认为执行该规则代价高昂),则该规则保留在标准中,并且违规行为被宣布为未定义行为。 永远不要触发未定义的行为是程序员的责任。编译器和相关工具有时可能会强制执行其中一些规则。一些编译器或相关工具甚至有可能一直强制执行其中一些规则。但是,一般来说,你是靠自己的。

Design and Evolution甚至包括对您的特定问题的讨论。编写完编译器后,Stroustrup 不想再编写链接器。他想出了一个依赖系统链接器的简单方法,但在早期,许多链接器对符号的长度有严格的限制。他努力说服编写系统链接器的人对它们进行足够的更改,以使它们能够与 Cfront 一起工作。最终,他成功了。

今天,链接器通常与编译器一起提供,但标准委员会仍将其视为单独的工具,可能超出编写编译器的人的控制范围。存在一个定义规则主要是为了确保编译器的输出与大多数标准链接器兼容,但委员会并不要求这些链接器能够检测到违反 ODR 的情况。此外,程序可能会以增量方式编译和链接,因此无法保证链接器将拥有足够的信息来检测违反 ODR 的情况。

值得注意的是,许多编译器inline会为函数和函数生成多个符号template,期望链接器抛出冗余定义。所以链接器看到多个定义不一定是错误。(就 ODR 而言,这些定义必须相同,以便链接器可以丢弃除一个之外的所有定义。)

它在库中允许它覆盖 ODR 怎么样?

我不会说“覆盖”。听起来这种行为是故意的。我会说“违反而不被发现。” 答案很简单,“标准委员会不希望链接器总是检测违规行为,而许多链接器也没有。” 避免违反 ODR 是您的责任。但是,您不是靠自己:命名空间的存在部分是为了使这个问题更容易解决。

于 2017-06-03T01:21:29.907 回答