我写了一个非常简单的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;
}
答案 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}}的定义,它有效地最终以递归方式调用自身。
然后唯一的解决方案就是选择一个不同的名称。