我有以下名为test.cpp的C ++测试程序:
#include <cmath>
#include <iostream>
double sqrt(double d) { return std::sqrt(d); }
int main()
{
std::cout << "sqrt(4): " << sqrt(4) << std::endl;
}
这是一些非常人为的代码,你可能已经猜到我只是想用Stroustrup做一个练习。他宣称双sqrt(双),并希望读者定义它。
我使用g ++ 4.8(来自Qt 5.1的MINGW版本)编译了上面的代码:
C:\Windows\Temp>g++ -o test.exe -g test.cpp
当我运行生成的可执行文件时,Windows 7说“test.exe已停止工作”。
要查看出了什么问题,我在GNU调试器中运行了test.exe。调试器命令和输出:
C:\Windows\Temp>gdb -q test.exe
Reading symbols from C:\Windows\Temp\test.exe...done.
(gdb) b main
Breakpoint 1 at 0x401625: file test.cpp, line 8.
(gdb) run
Starting program: C:\Windows\Temp\test.exe
[New Thread 12080.0x2ba0]
Breakpoint 1, main () at test.cpp:8
8 std::cout << "sqrt(4): " << sqrt(4) << std::endl;
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) q
A debugging session is active.
Inferior 1 [process 12080] will be killed.
Quit anyway? (y or n) y
C:\Windows\Temp>
从行为和警告中,我推断std :: sqrt必须从全局命名空间调用sqrt - 这会导致我的函数被重复调用。
通过更改sqrt函数的名称或将其放在命名空间内来解决不需要的递归是很容易的。但是我想理解为什么std :: sqrt以调用:: sqrt的方式实现。我认为std命名空间的重点是防止在用户代码中使用不合格的名称进行名称冲突。
我看了GNU implementation of <cmath>的源代码。然而,在跟随链中的几个#includes之后我失去了踪迹。也许你可以更好地理解它:
00052 #include <math.h>
00053
00054 // Get rid of those macros defined in <math.h> in lieu of real functions.
....
00076 #undef sqrt
....
00081 namespace std
00082 {
....
00393 using ::sqrt;
00394
00395 inline float
00396 sqrt(float __x)
00397 { return __builtin_sqrtf(__x); }
00398
00399 inline long double
00400 sqrt(long double __x)
00401 { return __builtin_sqrtl(__x); }
00402
00403 template<typename _Tp>
00404 inline typename __enable_if<double, __is_integer<_Tp>::_M_type>::_M_type
00405 sqrt(_Tp __x)
00406 { return __builtin_sqrt(__x); }
....
00437 }
顺便说一句,这不仅仅是一个GNU难题。使用Visual C ++编译器而不是g ++进行编译会产生以下警告:
C:\Windows\Temp>cl /nologo /EHsc test.cpp
test.cpp
c:\windows\temp\test.cpp(4) : warning C4717: 'sqrt' : recursive on all control
paths, function will cause runtime stack overflow
我想在StackOverflow上提出这个问题是个公平的问题。 :)
运行生成的可执行文件会导致预期结果:“test.exe已停止工作”。
答案 0 :(得分:6)
问题是从C标准库继承的函数,例如<cmath>
函数是有些有趣的野兽:它们看起来好像它们存在于命名空间std
中但实际上它们是是extern "C"
函数生活在全局命名空间中。基本上调用std::sqrt(x)
有效地调用::sqrt(x)
,这恰好是您刚定义的函数!
我还没有检查C ++标准在全局命名空间中对这些名称的描述,但我相当肯定它将它们归类为保留名称。也就是说,您最好不要以任何形状或形式定义::sqrt
。在合适的命名空间中定义函数,你会没事的。
好的,我查了一下。相关条款是17.3.24 [defns.reserved.function]:
保留功能
一个函数,指定为C ++标准库的一部分,必须由实现定义[注意:如果C ++程序为任何保留函数提供定义,则结果是未定义的。 - 后注]
......和17.6.4.3.3 [extern.names]第3和第4段:
使用外部链接声明的标准C库中的每个名称都保留给实现,以便在名称空间
extern "C"
和全局名称空间中用作std
链接的名称。使用外部链接声明的标准C库中的每个函数签名都保留给实现,以用作具有
extern "C"
和extern "C++"
链接的函数签名,或者作为命名空间范围的名称。全局命名空间。
答案 1 :(得分:6)
17.6.4.3.3 / 2 在标题中使用外部链接声明的每个全局函数签名都保留给实现,以指定具有外部链接的函数签名。
17.6.4.3.3 / 3 使用外部链接声明的标准C库中的每个名称都保留给实现,以用作名称extern "C"
的名称,在名称空间{{1}中并且在全局命名空间中 17.6.4.3.3 / 4 使用外部链接声明的标准C库中的每个函数签名都保留给实现,以用作std
和extern "C"
的函数签名链接,或作为全局命名空间中命名空间范围的名称。