重新定义标准库中的函数是否违反了单定义规则?

时间:2017-01-16 06:44:32

标签: c++ standards c++-standard-library one-definition-rule c-standard-library

#include <cmath>

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

假设log()中的函数<cmath>在全局命名空间中声明(事实上这是未指定的,我们只是做出这个假设),然后它引用与{{1}相同的函数我们定义的功能。
那么这段代码违反了单一定义规则(参见here,因为不需要诊断,这段代码可以在某些编译器中编译,如果它是正确的,我们就不能断言)?

注意:最近的修改后,这不是重复:What exactly is One Definition Rule in C++?

3 个答案:

答案 0 :(得分:3)

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

  

我猜这两个名称是指同一个实体,当且仅当它们具有相同的声明区域时,其中概念“声明区域”在标准中定义[...]这个猜测是否正确?标准中是否有任何支持此内容的词语?

通俗地称之为变量隐藏或阴影。标准说几乎逐字逐句。当前C ++ 17标准草案中的§3.3.10 ¶1

  

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

  

这段代码违反了单定义规则(参见此处,因为不需要诊断,这段代码可能会在某些编译器中编译,我们无法断言它是正确的)?

我不指望它。该标准要求所有 [RegularExpression("([A-Za-z]| *)*", ErrorMessage = "Use Alphabets Only")] 标头(特别是cheader)在cmath命名空间中引入其符号。将其注入全局命名空间的实现是标准符合(因为标准将该位留下未指定),但我会发现它的形式不好。你是可以发生的,这是正确的。现在,如果您要包含std(反对sage建议),那肯定会导致违反一个定义规则。

答案 1 :(得分:3)

典型情况。

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

至于UB的表现:将log视为弱连接符号显然很常见。您的实施将根据ABI规则覆盖libc.so

(如果实现没有extern "C",它仍然基本上都是一样的。)

其他可能的情况。

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

违反假设的情况。

  

然后它引用与我们定义的log函数相同的函数

<cmath>名称放入全局命名空间的实现的一种方法是在extern "C"内声明namespace std个函数,然后执行using namespace std,并确保这一点当包含任何标准头时,总是首先发生。由于using namespace不是“粘性” - 它仅适用于指定命名空间中的先前声明 - 标准库的其余部分不会显示。 (这不会声明全局命名空间中的名称,但标准只说“置于全局命名空间范围内。”)

在这样的实现中,您的声明将隐藏标准声明,并使用新的错位名称(例如_Z3logd而不是简单log)声明一个新函数和一个新的完全限定名称({ {1}}代替::log)。然后就不存在ODR违规(除非一些内联函数在一个TU中使用一个::std::log而在另一个TU中使用另一个)。

答案 2 :(得分:2)

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

#include <cmath>

double log(double) {return 1.0;}

int main()
{
    log(1.0);
}

没有违反ODR:

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

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

如果从数学库中提取包含log的对象模块并在程序中明确地链接它,情况会有所不同。因为对象模块总是包含在生成的程序中,而库中的对象模块只有在解析未定义的符号时才包含 conditionaly

标准参考:

草案n3337 for C ++ 11或n4296 for C ++ 14(或n4618 for last revision)在第2.2段中明确指出[lex.phases]:

  

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

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