5
#include <cmath>

double log(double) {return 1.0;}
int main() {
  log(1.0);
}

假设log()in的函数<cmath>声明在全局命名空间中(这实际上是未指定的,我们只是做这个假设),那么它引用的函数与log()我们定义的函数相同。
那么这段代码是否违反了单一定义规则(见这里,因为不需要诊断,这段代码可以在某些编译器中编译,我们无法断言它是否正确)?

注意:经过最近的编辑,这不是以下内容的重复:C++ 中的一个定义规则到底是什么?

4

3 回答 3

3

以下内容涉及 OP 的先前版本。我把它留在这里,以防将来的读者带着类似的查询来到这里。

我猜当且仅当两个名称具有相同的声明性区域时,两个名称才引用同一个实体,其中标准中定义了“声明性区域”的概念 [...] 这个猜测是否正确?标准中是否有任何文字支持这一点?

它通俗地称为变量隐藏或阴影。标准几乎一字不差地说明了你所说的。当前 C++17 标准草案中的§3.3.10 ¶1 :

可以通过在嵌套声明区域或派生类中显式声明相同名称来隐藏名称


那么这段代码是否违反了单一定义规则(见这里,因为不需要诊断,这段代码可以在某些编译器中编译,我们不能断言它是正确的)?

我不会期望它。该标准要求所有 cheader标题(尤其是cmath)在命名空间中引入它们的符号std。也将其倒入全局命名空间的实现是符合标准的(因为标准未指定该位),但我会发现格式不好。你是对的,它可能会发生。现在,如果您要包含math.h(反对圣人的建议),那肯定会导致违反单一定义规则。

于 2017-01-16T06:48:40.473 回答
3

典型场景。

如果extern "C" double log(double)最初是在全局命名空间中声明的,那么您已经重新声明了它并提供了定义。实现之前提到的会extern "C"延续到您的匹配重新声明。您的定义适用于属于实现的函数,它违反了 ODR。

至于 UB 的表现形式:将其log视为弱链接器符号显然很常见。libc.so您的实现将根据 ABI 规则进行覆盖。

(如果实现不做extern "C",基本上还是一样的。)

其他可能的情况。

如果log在其中声明namespace std然后带入全局命名空间,那么您的声明将与它发生冲突。(实际上,using声明在技术上就是声明。)诊断出此错误。

违反假设的场景。

log那么它指的是和我们定义的函数一样的函数

实现将<cmath>名称放入全局命名空间的一种方法是在内部声明extern "C"函数namespace std,然后执行using namespace std,并确保在包含任何标准标头时始终将其作为第一件事发生。由于using namespace不是“粘性的”——它只适用于指定命名空间中的前面声明——标准库的其余部分将不可见。(这不会在全局命名空间中声明名称,但标准只说“放置在全局命名空间范围内。”)

在这样的实现中,您的声明将隐藏标准的声明并声明一个具有新名称(例如_Z3logd,而不是simple log)和新的完全限定名称(::log而不是::std::log)的新函数。那么就不会有 ODR 违规(除非某些内联函数log在一个 TU 中使用一个,而另一个在不同的 TU 中使用)。

于 2017-01-16T08:39:56.277 回答
2

谨防。ODR 仅涉及将包含在结果程序中的定义。这意味着这与库中可能存在的符号无关,因为(普通)链接器不会加载整个库,而只会加载解析符号所需的部分。例如在这段代码中:

#include <cmath>

double log(double) {return 1.0;}

int main()
{
    log(1.0);
}

没有违反 ODR:

  • C 标准库中的日志符号仅包含在std命名空间中,并且根本没有冲突
  • 或者它也包含在全局命名空间中

在后一种情况下,声明与来自 cmath 的声明 double log(double)不冲突,因为它是相同的。并且由于符号log已经定义,它从标准库中的定义将不会包含在程序中。因此,log程序中只存在一个函数定义,即:double log(double) {return 1.0;}.

log如果您从数学库中提取包含的对象模块并将其显式链接到您的程序中,情况会有所不同。因为目标模块总是包含在结果程序中,而库中的目标模块只有在解析未定义的符号时才会有条件地包含。


标准参考:

C++11 的草案 n3337 或 C++14 的 n4296(或最新版本的 n4618)在第 2.2 段翻译阶段 [lex.phases] 中有明确说明:

§9。所有外部实体引用均已解析。链接库组件以满足对当前翻译中未定义的实体的外部引用。所有此类翻译器输出都被收集到程序映像中,该程序映像包含在其执行环境中执行所需的信息。

如图所示,代码仅使用一个翻译单元,并且log已在其中定义,因此不会使用库中的定义。

于 2017-01-16T08:48:31.293 回答