5

我有以下名为 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 进行练习。他声明了 double sqrt(double),并希望读者定义它。

我使用 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 命名空间的全部意义在于防止名称与用户代码中的非限定名称发生冲突。

我查看了<cmath> 的 GNU 实现的源代码。但是,在链中跟踪了几个#include 之后,我失去了踪迹。也许你可以更清楚地理解它:

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 已停止工作”。

4

2 回答 2

6

17.6.4.3.3/2在标头中用外部链接声明的每个全局函数签名都保留给实现,以指定具有外部链接的函数签名。
17.6.4.3.3/3使用外部链接声明的标准 C 库中的每个名称都保留给实现,以在命名空间和全局命名空间中用作具有extern "C"链接的名称。17.6.4.3.3/4使用外部链接声明的标准 C 库中的每个函数签名都保留给实现,以用作具有和链接的函数签名,或作为全局命名空间中命名空间范围的名称。std
extern "C"extern "C++"

于 2013-10-31T01:24:06.073 回答
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++"链接的函数签名,或作为全局命名空间中命名空间范围的名称。

于 2013-10-31T01:23:01.547 回答