Почему моя нерекурсивная функция sqrt рекурсивна?

У меня есть следующая тестовая программа C ++ под названием test.cpp:

#include <cmath>
#include <iostream>

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

int main()
{
std::cout << "sqrt(4): " << sqrt(4) << std::endl;
}

Это довольно надуманный код, и, как вы уже догадались, я просто пытаюсь выполнить упражнение из Страуструпа. Он объявил double sqrt (double) и хочет, чтобы читатель определил его.

Я скомпилировал приведенный выше код, используя g ++ 4.8 (из MINGW-релиза Qt 5.1):

C:\Windows\Temp>g++ -o test.exe -g test.cpp

Когда я запустил полученный исполняемый файл, Windows 7 сказала «test.exe перестал работать».

Чтобы увидеть, что пошло не так, я запустил test.exe в отладчике GNU. Команды отладчика и вывод:

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 реализация <CMATH>. Тем не менее, я потерял след после нескольких включений в цепочке. Может быть, вы можете иметь больше смысла в этом:

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 перестал работать».

5

Решение

Проблема в том, что функции, унаследованные от стандартной библиотеки 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:

Каждое имя из библиотеки Standard C, объявленной с внешней связью, зарезервировано для реализации для использования в качестве имени с extern "C" связь, как в пространстве имен std и в глобальном пространстве имен.

Каждая сигнатура функции из библиотеки Standard C, объявленная с помощью внешней связи, зарезервирована для реализации для использования в качестве сигнатуры функции с обоими extern "C" а также extern "C++" связь, или как имя области имен пространства в глобальном пространстве имен.

6

Другие решения

17.6.4.3.3 / 2 Каждая подпись глобальной функции, объявленная с внешней связью в заголовке, зарезервирована для реализации, чтобы обозначить эту подпись функции внешней связью.
17.6.4.3.3 / 3 Каждое имя из библиотеки Standard C, объявленной с внешней связью, зарезервировано для реализации для использования в качестве имени с extern "C" связь, как в пространстве имен std и в глобальном пространстве имен.
17.6.4.3.3 / 4 Каждая сигнатура функции из библиотеки Standard C, объявленная с помощью внешней связи, зарезервирована для реализации для использования в качестве сигнатуры функции с обоими extern "C" а также extern "C++" связь, или как имя области имен пространства в глобальном пространстве имен.

6