可能由命名冲突引起的未知错误?

时间:2013-03-24 21:18:15

标签: c++

我写了一个非常简单的c ++代码,我在其中定义了一个名为sqrt的函数,它只是调用 的std ::开方。出乎意料的是,我遇到了分段错误。如果我重命名,则问题不存在 函数sqrt作为其他东西。但是,从那以后我看不到任何命名冲突 我定义的sqrt函数不在命名空间std中,所以两者应该是完美的 分离。那么问题的真正原因是什么?谢谢!

#include<iostream>
#include<cmath>

double sqrt(double d);

double sqrt(double d) {
    return std::sqrt(d);
}

int main() {
    double x = 3.0;
    std::cout << "The square root of " << x << " is " << sqrt(x) << '\n';
    return 0;
}

3 个答案:

答案 0 :(得分:4)

<cmath>是一个有趣的标题。允许(但不要求)制作::sqrt和。{ std::sqrt个同义词。如果包含它,最好假设 两者都存在(或只包括<math.h>,其中 case,::sqrt就是你应该得到的。什么可能 在你的情况下发生的是1)std::sqrt实际上是一个 using的同义词(通过::sqrt),以及2)链接器正在挑选 首先提升你的::sqrt,这样你最终会得到无休止的递归。 唯一的解决方案,就是更改名称,就是把你的 <{1}}在命名空间中。

编辑:

请注意:上面是C ++ 11。早期版本的C ++使允许sqrt将任何内容引入全局命名空间。然而,所有的实现都做到了,所以标准被改变为祝福这种做法。 (我想这是让编译器符合标准的一种方法。)

编辑:

关于图书馆如何“捡起”的一些额外信息 符号,回应评论中的问题。从形式上看, 根据C ++标准,您可能没有两个定义 相同的函数(相同的名称,名称空间和参数类型) 在一个程序中。如果这两个定义是分开的 翻译单位,行为是不确定的。考虑到这一点, 有几个实际考虑因素。

第一个可以被认为是图书馆的定义(或者在 至少传统的定义)。图书馆是一套 模块 - 翻译单元,就标准而言。 (通常,但并非总是如此,模块由编译组成 目标文件。但是,在库中链接带来 在其中的所有模块中;来自图书馆的模块是 只有在解决了未解决的问题时才会合并到您的程序中 外部。因此,如果<cmath>已经定义(已解决) 在链接器查看库之前,该模块包含 图书馆中的::sqrt不会成为您计划的一部分。

在实践中,近年来滥用“图书馆”一词, 到了可以说它的含义发生了变化的程度。 特别是,微软称之为“动态加载 库“(以及Unix中所谓的”共享对象“) 之前),传统意义上的库,以及 以上不适用于他们。但是,其他问题确实如此 取决于动态加载程序的工作方式。在Unix的情况下, 如果几个共享对象具有相同的符号,则所有对象都将解析 到第一个加载(默认情况下 - 这可以控制 通过传递给::sqrt的选项。在Windows的情况下,由 默认情况下,如果可能,将在DLL中解析符号; 在您的情况下,如果dlopen是内联函数,或者是 指定为std::sqrt,这将是调用的DLL using ::sqrt;如果在标题中,它是std::sqrt, 这将是包含执行的DLL __declspec(dllexport)

最后,今天几乎所有的连接器都支持某种形式的弱点 引用。这通常用于模板实例化: 像std::sqrt这样的东西 在每个使用它的翻译单元中实例化,但是 一个“弱”的象征。然后链接器选择一个(可能是 首先它会遇到,但它没有指定),并抛出所有 其他人。虽然这种技术主要用于模板 实例化,编译器可以使用weak定义任何函数 引用(如果函数是内联的,将执行此操作)。在这 如果定义不同(如你的情况那样) std::vector<int>::vector( size_t, int )),那么我们可以真实地说该计划是非法的, 因为它违反了一个定义规则。但结果是 未定义的行为,不需要诊断。你呢 以不同方式定义内联函数或函数模板 两个不同的翻译单位,例如,你几乎就会 永远不会出错;如果编译器实际上没有内联 它们,链接器将选择一个,并在两个转换中使用它 单位。在你的情况下(::sqrt),我怀疑这是否适用; 我希望这是一个真正的库函数,而不是 内联。 (如果内联,则定义将在 标头::sqrt,你会得到一个重复的定义错误, 因为两个定义都在同一个翻译单元中。)

答案 1 :(得分:2)

问题似乎是<cmath>引入了sqrt名称(没有std::命名空间)以及std::sqrt。我担心你需要使用另一个名字。

请参阅此示例,使用GCC 4.8的快照:

#include<iostream>
#include<cmath>

int main() {
    double x = 9.0;
    std::cout << sqrt(x) << '\n'; // look, no std::sqrt
}

答案 2 :(得分:1)

按照第17.6.1.2/4段的规定:

  

除第18条至第30条和附件D中所述外,每个标题cname的内容应相同   与C标准库(1.2)或C Unicode中指定的相应头名称h相同   TR,视情况而定,如同包含一样。但是,在C ++标准库中,声明(除了   在C)中定义为宏的名称在命名空间std的命名空间范围(3.3.6)内。 是的   未指定是否首先在全局命名空间范围内声明这些名称,然后注入这些名称   通过显式使用声明进入命名空间std (7.3.3)。

另外,根据附件D.5 / 2:

  

每个C标头,每个标头都有一个名称为name.h的名称,的行为就像每个名称都放在标准中一样   相应的cname头的库命名空间放在全局命名空间范围内。它是   未指定是否首先在命名空间的命名空间范围(3.3.6)中声明或定义这些名称   std然后通过显式using-declarations(7.3.3)注入全局命名空间范围。

由于用于使全局函数可用的确切技术由实现决定,因此您的实现可能具有using指令,例如std命名空间中的namespace std { using ::sqrt; // ... } 指令:

std::sqrt

这意味着::sqrt实际上成为::sqrt的别名,并且您提供了{{1}}的定义,它有效地最终以递归方式调用自身。

然后唯一的解决方案就是选择一个不同的名称。